variable_screen.dart 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. import 'package:flutter/material.dart';
  2. import 'package:provider/provider.dart';
  3. import '../../viewmodels/variable_view_model.dart';
  4. import '../../models/models.dart';
  5. import 'variable_form.dart';
  6. import '../../themes/themes.dart';
  7. import '../../widgets/app_textfield.dart';
  8. import '../../widgets/widgets_components.dart' as clase;
  9. class VariablesScreen extends StatefulWidget {
  10. @override
  11. _VariablesScreenState createState() => _VariablesScreenState();
  12. }
  13. class _VariablesScreenState extends State<VariablesScreen> {
  14. final _busqueda = TextEditingController(text: '');
  15. ScrollController horizontalScrollController = ScrollController();
  16. @override
  17. void initState() {
  18. super.initState();
  19. Provider.of<VariableViewModel>(context, listen: false).fetchLocalAll();
  20. }
  21. void go(Variable variable) {
  22. Navigator.push(
  23. context,
  24. MaterialPageRoute(
  25. builder: (context) => VariableForm(variable: variable),
  26. ),
  27. ).then((_) =>
  28. Provider.of<VariableViewModel>(context, listen: false).fetchLocalAll());
  29. }
  30. void clearSearchAndReset() {
  31. setState(() {
  32. _busqueda.clear();
  33. Provider.of<VariableViewModel>(context, listen: false).fetchLocalAll();
  34. });
  35. }
  36. @override
  37. Widget build(BuildContext context) {
  38. final model = Provider.of<VariableViewModel>(context);
  39. double screenWidth = MediaQuery.of(context).size.width;
  40. final isMobile = screenWidth < 1250;
  41. final double? columnSpacing = isMobile ? null : 0;
  42. TextStyle estilo = const TextStyle(fontWeight: FontWeight.bold);
  43. List<DataRow> registros = [];
  44. for (Variable item in model.variables) {
  45. registros.add(DataRow(cells: [
  46. DataCell(
  47. Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
  48. PopupMenuButton(
  49. itemBuilder: (context) => [
  50. PopupMenuItem(
  51. child: const Text('Editar'),
  52. onTap: () => go(item),
  53. ),
  54. PopupMenuItem(
  55. child: const Text('Eliminar'),
  56. onTap: () async {
  57. await Future.delayed(Duration(milliseconds: 100));
  58. bool confirmado = await showDialog<bool>(
  59. context: context,
  60. builder: (context) {
  61. return AlertDialog(
  62. title: const Text("Eliminar",
  63. style: TextStyle(
  64. fontWeight: FontWeight.w500, fontSize: 22)),
  65. content: const Text(
  66. '¿Estás seguro de que deseas eliminar esta variable?',
  67. style: TextStyle(fontSize: 18)),
  68. actions: [
  69. Row(
  70. mainAxisAlignment:
  71. MainAxisAlignment.spaceBetween,
  72. children: [
  73. TextButton(
  74. onPressed: () =>
  75. Navigator.of(context).pop(false),
  76. child: const Text('No',
  77. style: TextStyle(fontSize: 18)),
  78. style: ButtonStyle(
  79. padding: MaterialStatePropertyAll(
  80. EdgeInsets.fromLTRB(
  81. 20, 10, 20, 10)),
  82. backgroundColor:
  83. MaterialStatePropertyAll(
  84. AppTheme.primary),
  85. foregroundColor:
  86. MaterialStatePropertyAll(
  87. AppTheme.secondary)),
  88. ),
  89. TextButton(
  90. onPressed: () =>
  91. Navigator.of(context).pop(true),
  92. child: const Text('Sí',
  93. style: TextStyle(fontSize: 18)),
  94. style: ButtonStyle(
  95. padding: MaterialStatePropertyAll(
  96. EdgeInsets.fromLTRB(
  97. 20, 10, 20, 10)),
  98. backgroundColor:
  99. MaterialStatePropertyAll(
  100. AppTheme.tertiary),
  101. foregroundColor:
  102. MaterialStatePropertyAll(
  103. AppTheme.quaternary)),
  104. ),
  105. ],
  106. )
  107. ],
  108. );
  109. },
  110. ) ??
  111. false;
  112. if (confirmado) {
  113. await model.deleteVariable(item.id!);
  114. model.fetchLocalAll();
  115. }
  116. },
  117. )
  118. ],
  119. icon: const Icon(Icons.more_vert),
  120. ),
  121. ])),
  122. DataCell(
  123. Text(item.nombre.toString()),
  124. onTap: () {
  125. Provider.of<VariableViewModel>(context, listen: false)
  126. .selectVariable(item);
  127. go(item);
  128. },
  129. ),
  130. DataCell(
  131. Text(item.clave.toString()),
  132. onTap: () {
  133. Provider.of<VariableViewModel>(context, listen: false)
  134. .selectVariable(item);
  135. go(item);
  136. },
  137. ),
  138. DataCell(
  139. Icon(
  140. item.activo == true ? Icons.check_circle : Icons.cancel,
  141. color: item.activo == true ? Colors.green : Colors.red,
  142. ),
  143. onTap: () {
  144. Provider.of<VariableViewModel>(context, listen: false)
  145. .selectVariable(item);
  146. go(item);
  147. },
  148. ),
  149. ]));
  150. }
  151. return Scaffold(
  152. appBar: AppBar(
  153. title: Text(
  154. 'Variables',
  155. style: TextStyle(color: AppTheme.secondary),
  156. ),
  157. iconTheme: IconThemeData(color: AppTheme.secondary),
  158. ),
  159. body: Column(
  160. children: [
  161. Expanded(
  162. child: ListView(
  163. padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
  164. children: [
  165. const SizedBox(height: 8),
  166. clase.tarjeta(
  167. Padding(
  168. padding: const EdgeInsets.all(8.0),
  169. child: LayoutBuilder(
  170. builder: (context, constraints) {
  171. if (screenWidth > 1000) {
  172. return Row(
  173. children: [
  174. Expanded(
  175. flex: 7,
  176. child: busquedaTextField(),
  177. ),
  178. SizedBox(width: 5),
  179. botonBuscar()
  180. ],
  181. );
  182. } else {
  183. return Column(
  184. children: [
  185. Row(
  186. children: [busquedaTextField()],
  187. ),
  188. SizedBox(height: 15),
  189. Row(
  190. children: [botonBuscar()],
  191. ),
  192. ],
  193. );
  194. }
  195. },
  196. ),
  197. ),
  198. ),
  199. const SizedBox(height: 8),
  200. model.isLoading
  201. ? const Center(child: CircularProgressIndicator())
  202. : Container(),
  203. clase.tarjeta(
  204. Column(
  205. children: [
  206. LayoutBuilder(builder: (context, constraints) {
  207. return SingleChildScrollView(
  208. scrollDirection: Axis.vertical,
  209. child: Scrollbar(
  210. controller: horizontalScrollController,
  211. interactive: true,
  212. thumbVisibility: true,
  213. thickness: 10.0,
  214. child: SingleChildScrollView(
  215. controller: horizontalScrollController,
  216. scrollDirection: Axis.horizontal,
  217. child: ConstrainedBox(
  218. constraints: BoxConstraints(
  219. minWidth: isMobile
  220. ? constraints.maxWidth
  221. : screenWidth),
  222. child: DataTable(
  223. columnSpacing: columnSpacing,
  224. sortAscending: true,
  225. sortColumnIndex: 1,
  226. columns: [
  227. DataColumn(label: Text(" ", style: estilo)),
  228. DataColumn(
  229. label: Text("NOMBRE", style: estilo)),
  230. DataColumn(
  231. label: Text("CLAVE", style: estilo)),
  232. DataColumn(
  233. label: Text("ACTIVO", style: estilo)),
  234. ],
  235. rows: registros,
  236. ),
  237. ),
  238. ),
  239. ),
  240. );
  241. }),
  242. ],
  243. ),
  244. ),
  245. const SizedBox(
  246. height: 15,
  247. ),
  248. if (!model.isLoading) ...[
  249. Row(
  250. mainAxisAlignment: MainAxisAlignment.center,
  251. children: [
  252. TextButton(
  253. onPressed:
  254. model.currentPage > 1 ? model.previousPage : null,
  255. child: Text('Anterior'),
  256. style: ButtonStyle(
  257. backgroundColor:
  258. MaterialStateProperty.resolveWith<Color?>(
  259. (Set<MaterialState> states) {
  260. if (states.contains(MaterialState.disabled)) {
  261. return Colors.grey;
  262. }
  263. return AppTheme.tertiary;
  264. },
  265. ),
  266. foregroundColor:
  267. MaterialStateProperty.resolveWith<Color?>(
  268. (Set<MaterialState> states) {
  269. if (states.contains(MaterialState.disabled)) {
  270. return Colors.black;
  271. }
  272. return Colors.white;
  273. },
  274. ),
  275. ),
  276. ),
  277. SizedBox(width: 15),
  278. Text(
  279. 'Página ${model.currentPage} de ${model.totalPages}'),
  280. SizedBox(width: 15),
  281. TextButton(
  282. onPressed: model.currentPage < model.totalPages
  283. ? model.nextPage
  284. : null,
  285. child: Text('Siguiente'),
  286. style: ButtonStyle(
  287. backgroundColor:
  288. MaterialStateProperty.resolveWith<Color?>(
  289. (Set<MaterialState> states) {
  290. if (states.contains(MaterialState.disabled)) {
  291. return Colors.grey;
  292. }
  293. return AppTheme.tertiary;
  294. },
  295. ),
  296. foregroundColor:
  297. MaterialStateProperty.resolveWith<Color?>(
  298. (Set<MaterialState> states) {
  299. if (states.contains(MaterialState.disabled)) {
  300. return Colors.black;
  301. }
  302. return Colors.white;
  303. },
  304. ),
  305. ),
  306. ),
  307. ],
  308. ),
  309. ],
  310. const SizedBox(
  311. height: 15,
  312. ),
  313. ],
  314. ),
  315. ),
  316. ],
  317. ),
  318. floatingActionButton: FloatingActionButton.extended(
  319. onPressed: () async {
  320. Variable nuevaVariable = Variable();
  321. Navigator.push(
  322. context,
  323. MaterialPageRoute(
  324. builder: (context) => VariableForm(variable: nuevaVariable),
  325. ),
  326. ).then((_) => Provider.of<VariableViewModel>(context, listen: false)
  327. .fetchLocalAll());
  328. },
  329. icon: Icon(Icons.add, size: 30, color: AppTheme.quaternary),
  330. label: Text(
  331. "Agregar Variable",
  332. style: TextStyle(fontSize: 18, color: AppTheme.quaternary),
  333. ),
  334. shape: RoundedRectangleBorder(
  335. borderRadius: BorderRadius.circular(8),
  336. ),
  337. materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
  338. backgroundColor: AppTheme.tertiary,
  339. ),
  340. );
  341. }
  342. Widget busquedaTextField() {
  343. return Row(
  344. children: [
  345. Expanded(
  346. flex: 3,
  347. child: AppTextField(
  348. prefixIcon: const Icon(Icons.search),
  349. etiqueta: 'Búsqueda por nombre...',
  350. controller: _busqueda,
  351. hintText: 'Búsqueda por nombre...',
  352. ),
  353. ),
  354. const SizedBox(width: 5),
  355. ],
  356. );
  357. }
  358. Widget botonBuscar() {
  359. return Expanded(
  360. flex: 2,
  361. child: Row(
  362. children: [
  363. Expanded(
  364. flex: 2,
  365. child: Padding(
  366. padding: const EdgeInsets.only(top: 30),
  367. child: ElevatedButton(
  368. onPressed: clearSearchAndReset,
  369. style: ElevatedButton.styleFrom(
  370. shape: RoundedRectangleBorder(
  371. borderRadius: BorderRadius.circular(20.0),
  372. ),
  373. primary: AppTheme.tertiary,
  374. padding: const EdgeInsets.symmetric(vertical: 25),
  375. ),
  376. child: Text('Limpiar',
  377. style: TextStyle(color: AppTheme.quaternary)),
  378. ),
  379. ),
  380. ),
  381. const SizedBox(width: 8),
  382. Expanded(
  383. flex: 2,
  384. child: Padding(
  385. padding: const EdgeInsets.only(top: 30),
  386. child: ElevatedButton(
  387. onPressed: () async {
  388. if (_busqueda.text.isNotEmpty) {
  389. await Provider.of<VariableViewModel>(context,
  390. listen: false)
  391. .fetchLocalByName(nombre: _busqueda.text.trim());
  392. } else {
  393. ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
  394. content: Text('Introduce un nombre para buscar.')));
  395. }
  396. },
  397. style: ElevatedButton.styleFrom(
  398. shape: RoundedRectangleBorder(
  399. borderRadius: BorderRadius.circular(20.0),
  400. ),
  401. primary: AppTheme.tertiary,
  402. padding: const EdgeInsets.symmetric(vertical: 25),
  403. ),
  404. child: Text('Buscar',
  405. style: TextStyle(color: AppTheme.quaternary)),
  406. ),
  407. ),
  408. ),
  409. ],
  410. ));
  411. }
  412. }