vue-laravel-crud 1.8.5 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,82 +1,214 @@
1
1
  <script>
2
- import draggable from "vuedraggable";
3
- import moment from "moment";
4
- import InfiniteLoading from 'vue-infinite-loading';
5
- import VueMasonry from 'vue-masonry-css'
6
- import ItemCard from "./ItemCard.vue";
7
-
8
-
2
+ import CrudHeader from "./components/CrudHeader.vue";
3
+ import CrudTable from "./components/CrudTable.vue";
4
+ import CrudCards from "./components/CrudCards.vue";
5
+ import CrudKanban from "./components/CrudKanban.vue";
6
+ import CrudCustom from "./components/CrudCustom.vue";
7
+ import CrudModals from "./components/CrudModals.vue";
8
+ import CrudPagination from "./components/CrudPagination.vue";
9
+
10
+ // Import mixins
11
+ import crudData from "./mixins/crudData.js";
12
+ import crudApi from "./mixins/crudApi.js";
13
+ import crudFilters from "./mixins/crudFilters.js";
14
+ import crudValidation from "./mixins/crudValidation.js";
15
+ import crudHelpers from "./mixins/crudHelpers.js";
9
16
 
10
17
  export default /*#__PURE__*/ {
11
18
  name: "VueLaravelCrud",
12
19
  components: {
13
- draggable,
14
- InfiniteLoading,
15
- VueMasonry,
16
- ItemCard
20
+ CrudHeader,
21
+ CrudTable,
22
+ CrudCards,
23
+ CrudKanban,
24
+ CrudCustom,
25
+ CrudModals,
26
+ CrudPagination
17
27
  },
18
- data() {
28
+ mixins: [
29
+ crudData,
30
+ crudApi,
31
+ crudFilters,
32
+ crudValidation,
33
+ crudHelpers
34
+ ],
35
+ provide() {
19
36
  return {
20
-
21
- crudUuid: "",
22
- moment: moment,
23
- loading: false,
24
- firstLoad: false,
25
- item: {
26
- id: null,
27
- },
28
- items: [],
29
- selectedItems: [],
30
- pagination: {
31
-
32
- current_page: 1,
33
- last_page: 1,
34
- next_page_url: "",
35
- prev_page_url: "",
36
- per_page: 20,
37
- total: 0,
38
- },
39
- displaySearch: false,
40
- itemDefault: null,
41
- filters: [],
42
- filtersVisible: false,
43
- filterSidebarOpen: false,
44
- internalFilters: [],
45
- forceRecomputeCounter: 0,
46
-
47
- displayModes: {
48
- MODE_TABLE: 1,
49
- MODE_CARDS: 2,
50
- MODE_CUSTOM: 3,
51
- MODE_KANBAN: 4
52
- },
53
- infiniteScrollKey: 1,
54
- optionsLoaded: false,
55
- isMobile: false,
56
- refreshing: false,
57
- fetchError: false,
58
- principalSort: false,
59
- exportFormat: 'JSON',
37
+ // Props
38
+ modelName: this.modelName,
39
+ title: this.title,
40
+ model: this.model,
41
+ models: this.models,
42
+ ajax: this.ajax,
43
+ useVuexORM: this.useVuexORM,
44
+ vuexInitRelations: this.vuexInitRelations,
45
+ vuexLocalforage: this.vuexLocalforage,
46
+ columns: this.columns,
47
+ filter: this.filter,
48
+ enableFilters: this.enableFilters,
49
+ infiniteScroll: this.infiniteScroll,
50
+ sortable: this.sortable,
51
+ orderable: this.orderable,
52
+ validate: this.validate,
53
+ orderProp: this.orderProp,
54
+ createMultipart: this.createMultipart,
55
+ apiUrl: this.apiUrl,
56
+ search: this.search,
57
+ hideModalAfterSave: this.hideModalAfterSave,
58
+ hideModalAfterCreate: this.hideModalAfterCreate,
59
+ hideModalAfterUpdate: this.hideModalAfterUpdate,
60
+ refreshAfterSave: this.refreshAfterSave,
61
+ showPaginator: this.showPaginator,
62
+ showCreateBtn: this.showCreateBtn,
63
+ showSearch: this.showSearch,
64
+ showPrincipalSortBtn: this.showPrincipalSortBtn,
65
+ showHeader: this.showHeader,
66
+ showTitle: this.showTitle,
67
+ limit: this.limit,
68
+ displayMode: this.displayMode,
69
+ displayModeToggler: this.displayModeToggler,
70
+ colXs: this.colXs,
71
+ colSm: this.colSm,
72
+ colMd: this.colMd,
73
+ colLg: this.colLg,
74
+ colXl: this.colXl,
75
+ selectHover: this.selectHover,
76
+ selectClick: this.selectClick,
77
+ cardClass: this.cardClass,
78
+ listContainerClass: this.listContainerClass,
79
+ listItemClass: this.listItemClass,
80
+ cardHideFooter: this.cardHideFooter,
81
+ messageRemoveConfirm: this.messageRemoveConfirm,
82
+ messageRemoveBulkConfirm: this.messageRemoveBulkConfirm,
83
+ messageRemove: this.messageRemove,
84
+ messageNew: this.messageNew,
85
+ messageImport: this.messageImport,
86
+ messageExport: this.messageExport,
87
+ messageEmptyResults: this.messageEmptyResults,
88
+ messageNoMore: this.messageNoMore,
89
+ messageLoading: this.messageLoading,
90
+ messageSave: this.messageSave,
91
+ messageDefaultValidationError: this.messageDefaultValidationError,
92
+ searchPlaceholder: this.searchPlaceholder,
93
+ tableContainerClass: this.tableContainerClass,
94
+ tableClass: this.tableClass,
95
+ grouped: this.grouped,
96
+ groupedAttribute: this.groupedAttribute,
97
+ groupedLabelPre: this.groupedLabelPre,
98
+ groupedLabelAfter: this.groupedLabelAfter,
99
+ groupedSplit: this.groupedSplit,
100
+ draggableGroup: this.draggableGroup,
101
+ draggableOptions: this.draggableOptions,
102
+ masonryEnabled: this.masonryEnabled,
103
+ masonrySort: this.masonrySort,
104
+ masonryColumns: this.masonryColumns,
105
+ principalSortColumn: this.principalSortColumn,
106
+ bulkDelete: this.bulkDelete,
107
+ showImport: this.showImport,
108
+ showExport: this.showExport,
109
+ fileImport: this.fileImport,
110
+ markDirty: this.markDirty,
111
+
112
+ // Data from mixins
113
+ crudUuid: this.crudUuid,
114
+ moment: this.moment,
115
+ loading: this.loading,
116
+ firstLoad: this.firstLoad,
117
+ item: this.item,
118
+ items: this.items,
119
+ selectedItems: this.selectedItems,
120
+ pagination: this.pagination,
121
+ displaySearch: this.displaySearch,
122
+ itemDefault: this.itemDefault,
123
+ filters: this.filters,
124
+ filtersVisible: this.filtersVisible,
125
+ filterSidebarOpen: this.filterSidebarOpen,
126
+ internalFilters: this.internalFilters,
127
+ forceRecomputeCounter: this.forceRecomputeCounter,
128
+ displayModes: this.displayModes,
129
+ infiniteScrollKey: this.infiniteScrollKey,
130
+ optionsLoaded: this.optionsLoaded,
131
+ isMobile: this.isMobile,
132
+ refreshing: this.refreshing,
133
+ fetchError: this.fetchError,
134
+ principalSort: this.principalSort,
135
+ exportFormat: this.exportFormat,
136
+
137
+ // Computed from mixins
138
+ itemValue: this.itemValue,
139
+ isSplitGroups: this.isSplitGroups,
140
+ itemsList: this.itemsList,
141
+ paginationIndexStart: this.paginationIndexStart,
142
+ paginationIndexEnd: this.paginationIndexEnd,
143
+ finalFilters: this.finalFilters,
144
+ sortFilter: this.sortFilter,
145
+ groupFilter: this.groupFilter,
146
+ internalFilter: this.internalFilter,
147
+ internalFilterByProp: this.internalFilterByProp,
148
+ columnOptions: this.columnOptions,
149
+
150
+ // Methods from mixins
151
+ handleResize: this.handleResize,
152
+ rearrangeArray: this.rearrangeArray,
153
+ clearItems: this.clearItems,
154
+ updateData: this.updateData,
155
+ externalUpdate: this.externalUpdate,
156
+ makePagination: this.makePagination,
157
+ fetchItemsVuex: this.fetchItemsVuex,
158
+ fetchItemsLocal: this.fetchItemsLocal,
159
+ fetchItems: this.fetchItems,
160
+ groupItems: this.groupItems,
161
+ saveItemVuex: this.saveItemVuex,
162
+ saveItemLocal: this.saveItemLocal,
163
+ saveItem: this.saveItem,
164
+ deleteItem: this.deleteItem,
165
+ deleteItemLocal: this.deleteItemLocal,
166
+ deleteItemVuex: this.deleteItemVuex,
167
+ deleteItemBulk: this.deleteItemBulk,
168
+ deleteItemBulkLocal: this.deleteItemBulkLocal,
169
+ deleteItemBulkVuex: this.deleteItemBulkVuex,
170
+ saveSort: this.saveSort,
171
+ exportItems: this.exportItems,
172
+ importItems: this.importItems,
173
+ refresh: this.refresh,
174
+ onPaginationChange: this.onPaginationChange,
175
+ infiniteHandler: this.infiniteHandler,
176
+ setupFilters: this.setupFilters,
177
+ toggleSortFilter: this.toggleSortFilter,
178
+ toggleFilters: this.toggleFilters,
179
+ resetFilters: this.resetFilters,
180
+ isColumnHasFilter: this.isColumnHasFilter,
181
+ setFilter: this.setFilter,
182
+ onChangeFilter: this.onChangeFilter,
183
+ togglePrincipalSort: this.togglePrincipalSort,
184
+ loadOptions: this.loadOptions,
185
+ getArrayValue: this.getArrayValue,
186
+ getStateValue: this.getStateValue,
187
+ onRowHover: this.onRowHover,
188
+ onRowClick: this.onRowClick,
189
+ onSort: this.onSort,
190
+ onCheckSelect: this.onCheckSelect,
191
+ toggleAll: this.toggleAll,
192
+ unSelectItem: this.unSelectItem,
193
+ selectItem: this.selectItem,
194
+ getSelectedItems: this.getSelectedItems,
195
+ onSelect: this.onSelect,
196
+ showItem: this.showItem,
197
+ createItem: this.createItem,
198
+ updateItem: this.updateItem,
199
+ removeItem: this.removeItem,
200
+ confirmBulkDelete: this.confirmBulkDelete,
201
+ toggleDisplayMode: this.toggleDisplayMode,
202
+ showExportModal: this.showExportModal,
203
+ showImportModal: this.showImportModal,
204
+ onDraggableAdded: this.onDraggableAdded,
205
+ onDraggableChange: this.onDraggableChange,
206
+ onDragEnd: this.onDragEnd,
207
+ toastError: this.toastError,
208
+ toastSuccess: this.toastSuccess,
209
+ downloadBlobResponse: this.downloadBlobResponse
60
210
  };
61
211
  },
62
- watch: {
63
- search(val) {
64
- if (val && val != "") {
65
- this.filters = [];
66
- this.filters.push(["search", "LIKE", val]);
67
- this.fetchItems();
68
- } else {
69
- this.filters = [];
70
- this.fetchItems();
71
- }
72
- },
73
-
74
- models(val) {
75
- if (!this.ajax) {
76
- this.items = val;
77
- }
78
- },
79
- },
80
212
  props: {
81
213
  modelName: String,
82
214
 
@@ -388,1761 +520,22 @@ export default /*#__PURE__*/ {
388
520
  }
389
521
  },
390
522
 
391
- mounted() {
392
- const now = Math.floor(Date.now() / 1000);
393
- this.crudUuid = '' + now;
394
- this.isMobile = window.matchMedia("(max-width: 1024px)").matches;
395
-
396
- // Agregar un oyente de eventos para actualizar isMobile cuando cambia el tamaño de la pantalla
397
- window.addEventListener("resize", this.handleResize);
398
-
399
- if (this.useVuexORM) {
400
-
401
- if (this.vuexLocalforage) {
402
- this.item = {};
403
- } else {
404
- this.item = new this.model();
405
- }
406
- let itemVuexOrmDefault = {};
407
- const fields = this.model.fields();
408
- // Inicializa el objeto "itemDefault" con los valores por defecto
409
- const itemDefault = {};
410
- const primaryKey = this.model.primaryKey;
411
-
412
- for (const fieldName of Object.keys(fields)) {
413
- const field = fields[fieldName];
414
- if (fieldName === primaryKey) {
415
- continue; // Salta este campo
416
- }
417
-
418
- console.debug("debug field", field);
419
-
420
- if (field.type === 'relation') {
421
- // Si es una relación, inicializa como un objeto vacío.
422
- console.debug("Relation", field);
423
-
424
- if (this.vuexInitRelations == true || (Array.isArray(this.vuexInitRelations) && this.vuexInitRelations.includes(fieldName))) {
425
- itemDefault[fieldName] = {};
426
- }
427
-
428
- } else {
429
-
430
-
431
- console.debug("Field", field);
432
-
433
-
434
- // Si no tiene un valor por defecto definido, inicializa según su tipo
435
- /*switch (field.constructor.name) {
436
- case 'StringField':
437
- itemDefault[fieldName] = '';
438
- break;
439
- case 'NumberField':
440
- itemDefault[fieldName] = 0;
441
- break;
442
- // Agrega más casos según los tipos de campos que uses en tu modelo
443
- default:
444
-
445
- console.debug("Undefined constructor ",fieldName,field.constructor.name);
446
- // Tipo de campo no reconocido, puedes manejarlo de acuerdo a tus necesidades
447
- itemDefault[fieldName] = null;
448
- }*/
449
-
450
-
451
- if (typeof field.value === 'function') {
452
- itemDefault[fieldName] = field.value();
453
- } else if (field.value) {
454
- itemDefault[fieldName] = field.value;
455
- } else {
456
- itemDefault[fieldName] = null;
457
- }
458
-
459
-
460
- }
461
- }
462
-
463
- this.itemDefault = JSON.parse(JSON.stringify(itemDefault));
464
-
465
-
466
- } else {
467
- this.item = this.model;
468
- this.itemDefault = JSON.parse(JSON.stringify(this.item));
469
- }
470
-
471
- console.debug("crud mounted columns", this.columns);
472
- this.internalFilters = [];
473
- this.setupFilters();
474
- this.fetchItems();
475
- this.loadOptions();
476
- },
477
- computed: {
478
- itemValue() {
479
- return (column, item) => {
480
- if (
481
- column.prop &&
482
- column.prop.split(".").length > 1 &&
483
- column.prop.split(".")[1]
484
- ) {
485
- return item[column.prop.split(".")[0]] &&
486
- item[column.prop.split(".")[0]][column.prop.split(".")[1]]
487
- ? item[column.prop.split(".")[0]][column.prop.split(".")[1]]
488
- : "";
489
- } else {
490
- return item[column.prop];
491
- }
492
- };
493
- },
494
-
495
- isSplitGroups(){
496
-
497
- if(this.groupedSplit){
498
- return true;
499
- }
500
- return this.displayMode == this.displayModes.MODE_KANBAN;
501
-
502
- },
503
- itemsList() {
504
- const items = this.ajax ? this.items : this.items.slice(this.paginationIndexStart, this.paginationIndexEnd);
505
- if (this.masonrySort && !this.isMobile) {
506
- return this.rearrangeArray(items, this.masonryColumns);
507
- }
508
- return items;
509
- },
510
- paginationIndexStart() {
511
- return (this.pagination.current_page - 1) * this.pagination.per_page;
512
- },
513
- paginationIndexEnd() {
514
- return this.paginationIndexStart + this.pagination.per_page;
515
- },
516
- finalFilters() {
517
- return [
518
- ...this.filters,
519
- ...this.filter,
520
- ...this.internalFilter,
521
- ...this.sortFilter,
522
- ...this.groupFilter
523
- ];
524
- },
525
- sortFilter() {
526
- if (this.showPrincipalSortBtn) {
527
- if (this.principalSort) {
528
- return [[this.principalSortColumn, 'SORTASC', '']];
529
- } else {
530
- return [[this.principalSortColumn, 'SORTDESC', '']];
531
- }
532
- } else {
533
- return [];
534
- }
535
-
536
- },
537
-
538
- groupFilter() {
539
- if (this.grouped && this.groupedAttribute) {
540
- return [['', 'GROUPBY', this.groupedAttribute]];
541
- } else {
542
- return [];
543
- }
544
- },
545
-
546
- internalFilter() {
547
- let filter = [];
548
- this.forceRecomputeCounter;
549
- this.internalFilters.forEach((f) => {
550
- if (f.value) {
551
- let colname = f.column.replace("_sort", "").replace("_from", "").replace("_to", "");
552
- filter.push([colname, f.op, f.value]);
553
- }
554
- });
555
- return filter;
556
- },
557
- internalFilterByProp() {
558
- return (prop) => {
559
- return this.internalFilters.find((inf) => inf.column == prop);
560
- };
561
- },
562
- columnOptions() {
563
- return (column) => {
564
-
565
- }
566
- }
567
- },
568
- methods: {
569
- handleResize() {
570
- // Actualizar isMobile cuando cambia el tamaño de la pantalla
571
- this.isMobile = window.matchMedia("(max-width: 1024px)").matches;
572
- },
573
-
574
- togglePrincipalSort() {
575
- this.principalSort = !this.principalSort;
576
- setTimeout(() => {
577
- this.refresh();
578
- }, 1);
579
- },
580
- infiniteHandler($state) {
581
-
582
-
583
- const hasNextPage = (this.pagination.total > 0 || !this.firstLoad) && (!this.firstLoad || (this.pagination.current_page * this.pagination.per_page) <= this.pagination.total);
584
- console.debug("Has next page", hasNextPage, this.pagination);
585
- if (hasNextPage) {
586
- const page = this.pagination.current_page + 1;
587
- this.fetchItems(page, true).then(() => {
588
- console.debug("infinite handler then");
589
- $state.loaded();
590
- }).catch(error => {
591
- console.debug("infinite handler error", error);
592
- $state.error();
593
- });
594
- } else {
595
- $state.complete();
596
- }
597
- },
598
- rearrangeArray(originalArray, columns = 3) {
599
- const rearrangedArray = [];
600
- for (let i = 0; i < columns; i++) {
601
- for (let j = i; j < originalArray.length; j += columns) {
602
- rearrangedArray.push(originalArray[j]);
603
- }
604
- }
605
- return rearrangedArray;
606
- },
607
- onDraggableAdded(event) {
608
- this.$emit("draggableAdded", event);
609
- },
610
- onDraggableChange(event) {
611
- this.$emit("draggableChange", event);
612
- },
613
- setupFilters() {
614
- this.columns.forEach((column) => {
615
- if (this.isColumnHasFilter(column)) {
616
- if (column.type == "date") {
617
- this.internalFilters.push({
618
- column: column.prop + "_from",
619
- op: column.filterOp ? column.filterOp : "=",
620
- value: null,
621
- });
622
-
623
- this.internalFilters.push({
624
- column: column.prop + "_to",
625
- op: column.filterOp ? column.filterOp : "=",
626
- value: null,
627
- });
628
- } else {
629
- this.internalFilters.push({
630
- column: column.prop,
631
- op: column.filterOp ? column.filterOp : "=",
632
- value: null,
633
- });
634
- }
635
- }
636
- if (this.sortable) {
637
- this.internalFilters.push({
638
- column: column.prop + "_sort",
639
- op: column.filterOp ? column.filterOp : "=",
640
- value: null,
641
- });
642
- }
643
- });
644
- },
645
-
646
- toggleSortFilter(column) {
647
- let value = this.internalFilterByProp(column.prop + "_sort").value;
648
- if (!value) {
649
- this.internalFilterByProp(column.prop + "_sort").value = "ASC";
650
- } else if (value == "ASC") {
651
- this.internalFilterByProp(column.prop + "_sort").value = "DESC";
652
- } else if (value == "DESC") {
653
- this.internalFilterByProp(column.prop + "_sort").value = null;
654
- }
655
- },
656
- toggleFilters() {
657
- this.filtersVisible = !this.filtersVisible;
658
- if (this.displayMode == this.displayModes.MODE_CARDS) {
659
- this.filterSidebarOpen = this.filtersVisible;
660
- } else {
661
- this.filterSidebarOpen = false;
662
- }
663
- },
664
- resetFilters(refresh = true) {
665
- this.internalFilters = [];
666
- this.setupFilters();
667
- this.forceRecomputeCounter++;
668
-
669
- if (refresh) {
670
- setTimeout(() => {
671
- this.refresh();
672
- }, 1);
673
- }
674
-
675
- },
676
- toggleDisplayMode() {
677
- if (this.displayMode == this.displayModes.MODE_TABLE)
678
- this.displayMode = this.displayModes.MODE_CARDS;
679
- else if (this.displayMode == this.displayModes.MODE_CARDS)
680
- this.displayMode = this.displayModes.MODE_TABLE;
681
- },
682
- onRowHover(item, itemIndex) {
683
- if (this.selectHover) {
684
- this.item = this.items[itemIndex];
685
- this.selectItem();
686
- this.onSelect();
687
- }
688
- },
689
- onRowClick(item, itemIndex) {
690
- if (this.selectClick) {
691
- this.item = this.items[itemIndex];
692
- this.selectItem();
693
- this.onSelect();
694
- }
695
- },
696
- onSort() {
697
- let event = {};
698
- let i =
699
- 1 +
700
- (this.pagination.current_page * this.pagination.per_page -
701
- this.pagination.per_page);
702
- this.items.forEach((item, index) => {
703
- //console.debug(s, i);
704
- item[this.orderProp] = i;
705
- i++;
706
- });
707
- this.$emit("sort", event);
708
- },
709
- onCheckSelect(value, item) {
710
- console.debug("ON CHECK SELECT", value, item);
711
- if (value) {
712
- this.item = item;
713
- this.selectItem();
714
- } else {
715
- this.unSelectItem(item);
716
- }
717
- this.onSelect();
718
- console.debug("Selected Items", this.selectedItems);
719
- },
720
- toggleAll(value) {
721
-
722
-
723
- if (value) {
724
- this.selectedItems = this.items;
725
-
726
- this.selectedItems.forEach(
727
- (item) => item.selected = true
728
- );
729
- } else {
730
- this.selectedItems.forEach(
731
- (item) => item.selected = false
732
- );
733
- this.items.forEach(
734
- (item) => item.selected = false
735
- );
736
- this.selectedItems = [];
737
-
738
- }
739
-
740
- this.onSelect();
741
-
742
- console.debug("toggle all", this.selectedItems);
743
- this.$forceUpdate();
744
- },
745
- unSelectItem(item) {
746
-
747
- item.selected = false;
748
-
749
- this.selectedItems = this.selectedItems.filter(
750
- (e) => e.id != item.id
751
- );
752
-
753
- },
754
- selectItem() {
755
- let sitem = this.selectedItems.find((e) => e.id == this.item.id);
756
- if (sitem) {
757
- this.item.selected = false;
758
- this.selectedItems = this.selectedItems.filter(
759
- (e) => e.id != this.item.id
760
- );
761
- } else {
762
- this.item.selected = true;
763
- this.selectedItems.push(this.item);
764
- }
765
- },
766
- externalUpdate(itemsUpdate, addIfNotExist = true, key = 'id') {
767
- itemsUpdate.forEach(itemUpdate => {
768
- let itemInList = this.items.find(item => item[key] === itemUpdate[key]);
769
- if (itemInList) Object.assign(itemInList, itemUpdate);
770
- else {
771
- if (addIfNotExist) {
772
- this.items.push(itemUpdate);
773
- }
774
- }
775
- });
776
- },
777
- getSelectedItems() {
778
- return this.selectedItems;
779
- },
780
- onSelect() {
781
- this.$emit("select", this.item);
782
- this.$emit("selectItems", this.selectedItems);
783
- },
784
-
785
- updateData(data, allowCreate = true) {
786
- // Convertir this.items a un mapa para acceso rápido por id
787
- const itemsMap = new Map(this.items.map(item => [item.id, item]));
788
-
789
- // Recorrer cada elemento de data
790
- data.forEach(newItem => {
791
- if (itemsMap.has(newItem.id)) {
792
- // Actualizar el item existente
793
- const existingItem = itemsMap.get(newItem.id);
794
- Object.assign(existingItem, newItem);
795
- } else if (allowCreate) {
796
- // Agregar el nuevo item si allowCreate es true
797
- this.items.push(newItem);
798
- }
799
- });
800
-
801
- // Convertir el mapa de vuelta a un array, si es necesario
802
- this.items = Array.from(itemsMap.values());
803
- },
804
- showItem(id, itemIndex = null) {
805
- if (itemIndex == null) {
806
- let item = this.items.find((it) => it.id == id);
807
- this.item = item;
808
- } else {
809
- this.item = this.items[itemIndex];
810
- }
811
- this.onSelect();
812
- this.$bvModal.show("modal-show-item-" + this.modelName);
813
- },
814
- createItem() {
815
- if (this.useVuexORM) {
816
-
817
- if (this.vuexLocalforage) {
818
- this.item = JSON.parse(JSON.stringify(this.itemDefault));
819
- } else {
820
- this.item = new this.model(JSON.parse(JSON.stringify(this.itemDefault)));
821
- }
822
-
823
- } else {
824
- this.item = JSON.parse(JSON.stringify(this.itemDefault));
825
- }
826
- this.onSelect();
827
- this.$bvModal.show("modal-form-item-" + this.modelName);
828
- },
829
- updateItem(id, itemIndex = null) {
830
- if (itemIndex == null) {
831
- let item = this.items.find((it) => it.id == id);
832
- this.item = item;
833
- } else {
834
- this.item = this.items[itemIndex];
835
- }
836
- //console.debug(itemIndex);
837
- this.onSelect();
838
- this.$bvModal.show("modal-form-item-" + this.modelName);
839
- },
840
-
841
- refresh() {
842
- this.$emit("refresh", {});
843
-
844
- if (this.infiniteScroll) {
845
- this.pagination.current_page = 1;
846
- this.infiniteScrollKey++;
847
- }
848
-
849
- const fetchPromise = this.fetchItems(this.pagination.current_page);
850
-
851
- if (this.infiniteScroll && fetchPromise) {
852
- this.refreshing = true;
853
- fetchPromise.then(() => {
854
- const infiniteLoadingRef = this.$refs.infiniteLoading;
855
- if (infiniteLoadingRef) {
856
- infiniteLoadingRef.stateChanger.reset();
857
- } else {
858
- console.debug("infiniteLoadingRef not set");
859
- }
860
- this.refreshing = false;
861
- });
862
- }
863
- },
864
- isColumnHasFilter(column) {
865
- return column && !column.hideFilter && column.type != "actions";
866
- },
867
- setFilter(column, value) {
868
- let filter = this.filter.find((f) => f.column == column);
869
- filter.value = value;
870
- this.forceRecomputeCounter++;
871
- setTimeout(() => {
872
- this.refresh();
873
- }, 1);
874
- },
875
- async fetchItemsVuex(page = 1, concat = false) {
876
- this.loading = true;
877
- this.$emit("beforeFetch", {});
878
-
879
- let result;
880
-
881
- if (this.vuexLocalforage) {
882
- await this.model.$fetch();
883
-
884
- } else {
885
- this.model.deleteAll();
886
-
887
- result = await this.model.api().get(this.apiUrl + "/" + this.modelName, {
888
- dataKey: 'data',
889
- params: {
890
- page: page,
891
- limit: this.pagination.perPage,
892
- filters: JSON.stringify(this.finalFilters),
893
- }
894
- });
895
-
896
- }
897
-
898
- let itemsResult = this.model.query().withAll().get();
899
- //let itemsResult = result.entities[this.model.entity];
900
-
901
- if (itemsResult) {
902
- this.items = itemsResult;
903
- }
904
- console.debug("fetch page vuex ", itemsResult, page, this.items, result);
905
- this.loading = false;
906
- this.firstLoad = true;
907
- },
908
- fetchItemsLocal() {
909
- if (this.grouped) {
910
- this.groupItems(this.models);
911
- } else {
912
- this.items = this.models;
913
- }
914
-
915
- this.pagination.total = this.items.length;
916
- this.firstLoad = true;
917
-
918
- },
919
- fetchItems(page = 1, concat = false) {
920
-
921
-
922
- this.$emit("beforeFetch", {});
923
- if (this.useVuexORM) {
924
- return this.fetchItemsVuex(page, concat);
925
- }
926
-
927
-
928
- if (!this.ajax) {
929
- return this.fetchItemsLocal(page, concat);
930
- }
931
-
932
- this.loading = true;
933
- return axios
934
- .get(this.apiUrl + "/" + this.modelName, {
935
- params: {
936
- page: page,
937
- limit: this.limit,
938
- filters: JSON.stringify(this.finalFilters),
939
- },
940
- })
941
- .then((response) => {
942
- this.makePagination(response.data);
943
- let items = response.data.data;
944
- if (this.grouped) {
945
- //this.items = items;
946
- this.groupItems(items, concat,this.isSplitGroups);
947
- } else {
948
- if (concat) {
949
- this.items = this.items.concat(items);
950
- } else {
951
- this.items = items;
952
- }
953
- }
954
-
955
- this.loading = false;
956
- this.firstLoad = true;
957
- this.$emit("afterFetch", {});
958
- })
959
- .catch((error) => {
960
- //console.debug(error);
961
- this.toastError(error);
962
- this.loading = false;
963
- this.firstLoad = true;
964
- this.fetchError = true;
965
- });
966
- },
967
- groupItems(items, concat = false, splitGroups = false) {
968
- const groupedAttribute = this.groupedAttribute;
969
- const groupLabelPre = this.groupedLabelPre || '';
970
- const groupLabelAfter = this.groupedLabelAfter || '';
971
- const itemsWithGroup = [];
972
-
973
- // Usamos un objeto para agrupar los elementos por groupedAttribute
974
- const groupedMap = items.reduce((acc, item) => {
975
- const groupKey = item[groupedAttribute] || 'undefined';
976
- if (!acc[groupKey]) {
977
- acc[groupKey] = [];
978
- }
979
- acc[groupKey].push(item);
980
- return acc;
981
- }, {});
982
-
983
- if (splitGroups) {
984
- // Dividimos los grupos en arrays separados
985
- this.items = Object.entries(groupedMap).map(([groupKey, groupItems]) => ({
986
- groupKey,
987
- groupLabel: groupLabelPre + groupKey + groupLabelAfter,
988
- items: groupItems,
989
- }));
990
- } else {
991
- // Creamos la estructura agrupada en un solo array
992
- for (const [groupKey, groupItems] of Object.entries(groupedMap)) {
993
- itemsWithGroup.push({
994
- crudgrouplabel: groupLabelPre + groupKey + groupLabelAfter,
995
- crudgroup: true,
996
- });
997
- itemsWithGroup.push(...groupItems);
998
- }
999
-
1000
- // Decidimos si concatenar o reemplazar los items existentes
1001
- if (concat) {
1002
- this.items = this.items.concat(itemsWithGroup);
1003
- } else {
1004
- this.items = itemsWithGroup;
1005
- }
1006
- }
1007
- },
1008
- removeItem(id, index) {
1009
- this.$bvModal
1010
- .msgBoxConfirm(this.messageRemoveConfirm, {
1011
- size: "sm",
1012
- buttonSize: "sm",
1013
- okVariant: "danger",
1014
- okTitle: this.messageRemove,
1015
- cancelTitle: "NO",
1016
- centered: true,
1017
- })
1018
- .then((value) => {
1019
- if (value) {
1020
- this.deleteItem(id, index);
1021
- }
1022
- })
1023
- .catch((error) => {
1024
- this.toastError(error);
1025
- this.loading = false;
1026
- });
1027
- },
1028
-
1029
- confirmBulkDelete() {
1030
- this.$bvModal
1031
- .msgBoxConfirm(this.messageRemoveBulkConfirm, {
1032
- size: "sm",
1033
- buttonSize: "sm",
1034
- okVariant: "danger",
1035
- okTitle: this.messageRemove,
1036
- cancelTitle: "NO",
1037
- centered: true,
1038
- })
1039
- .then((value) => {
1040
- if (value) {
1041
- this.deleteItemBulk();
1042
- }
1043
- })
1044
- .catch((error) => {
1045
- this.toastError(error);
1046
- this.loading = false;
1047
- });
1048
- },
1049
- deleteItemBulk() {
1050
- if (this.useVuexORM) {
1051
- return this.deleteItemBulkVuex();
1052
- }
1053
-
1054
- if (!this.ajax) {
1055
- return this.deleteItemBulkLocal();
1056
- }
1057
-
1058
-
1059
- let ids = this.selectedItems.map(it => it.id);
1060
-
1061
- this.loading = true;
1062
- axios
1063
- .delete(this.apiUrl + "/" + this.modelName + "/bulk-destroy", { params: { ids: ids }, })
1064
- .then((response) => {
1065
- this.items = this.items.filter(it => !ids.includes(it.id));
1066
- this.toastSuccess("Elemento/s eliminado.");
1067
- this.$emit("itemDeleted", {});
1068
- this.loading = false;
1069
- })
1070
- .catch((error) => {
1071
- this.toastError(error);
1072
- this.loading = false;
1073
- });
1074
- },
1075
- async deleteItemBulkLocal() {
1076
- let ids = this.selectedItems.map(it => it.id);
1077
- this.items = this.items.filter(it => !ids.includes(it.id));
1078
- this.item = null;
1079
- this.toastSuccess("Elemento Eliminado");
1080
- this.$emit("itemDeleted", {});
1081
- this.loading = false;
1082
- },
1083
- async deleteItemBulkVuex() {
1084
-
1085
- let ids = this.selectedItems.map(it => it.id);
1086
-
1087
-
1088
- if (this.vuexLocalforage) {
1089
- await this.model.$delete(ids);
1090
-
1091
- } else {
1092
- let result = await this.model.api().delete(this.apiUrl + "/" + this.modelName + '/bulk-destroy', {
1093
- params: { ids: ids },
1094
- delete: ids
1095
- });
1096
-
1097
- console.debug("delete item vuex", result);
1098
- let responseStatus = result.response.status;
1099
-
1100
- if (result.response.data.error) {
1101
- this.toastError(result.response.data.error);
1102
- this.loading = false;
1103
- return;
1104
- }
1105
- }
1106
-
1107
- this.toastSuccess("Elemento eliminados.");
1108
- },
1109
- deleteItem(id, index) {
1110
-
1111
- if (this.useVuexORM) {
1112
- return this.deleteItemVuex(id, index);
1113
- }
1114
-
1115
- if (!this.ajax) {
1116
- return this.deleteItemLocal(id, index);
1117
- }
1118
-
1119
- this.loading = true;
1120
- axios
1121
- .delete(this.apiUrl + "/" + this.modelName + "/" + id)
1122
- .then((response) => {
1123
- this.items.splice(index, 1);
1124
- this.toastSuccess("Elemento eliminado.");
1125
- this.$emit("itemDeleted", {});
1126
- this.loading = false;
1127
- })
1128
- .catch((error) => {
1129
- this.toastError(error);
1130
- this.loading = false;
1131
- });
1132
- },
1133
-
1134
-
1135
- async deleteItemLocal(id, index) {
1136
- if (id || index) {
1137
- let itemIndex;
1138
-
1139
- if (id) {
1140
- itemIndex = this.items.findIndex((item) => item.id == this.item.id);
1141
- } else {
1142
- itemIndex = index;
1143
- }
1144
-
1145
- // Assuming this.items is an array
1146
- this.items.splice(itemIndex, 1);
1147
- this.item = null;
1148
- this.toastSuccess("Elemento Eliminado");
1149
- this.$emit("itemDeleted", {});
1150
-
1151
- } else {
1152
- // Handle the case where there's no item.id or item.index
1153
- console.error("Cannot delete item without ID or index");
1154
- // You might want to show an error message or handle it in a way that fits your application.
1155
- }
1156
-
1157
- this.loading = false;
1158
- },
1159
- async deleteItemVuex(id, index) {
1160
-
1161
-
1162
- if (this.vuexLocalforage) {
1163
- await this.model.$delete(id);
1164
-
1165
- } else {
1166
- let result = await this.model.api().delete(this.apiUrl + "/" + this.modelName + '/' + id, {
1167
- delete: 1
1168
- });
1169
-
1170
- console.debug("delete item vuex", result);
1171
- let responseStatus = result.response.status;
1172
-
1173
- if (result.response.data.error) {
1174
- this.toastError(result.response.data.error);
1175
- this.loading = false;
1176
- return;
1177
- }
1178
- }
1179
-
1180
-
1181
-
1182
- this.toastSuccess("Elemento eliminado.");
1183
- },
1184
- saveSort() {
1185
- if (this.orderable) {
1186
- this.loading = true;
1187
- let order = [];
1188
- this.items.forEach((v, k) => {
1189
- order.push({ id: v.id, order: v[this.orderProp] });
1190
- });
1191
-
1192
- if (!this.ajax) {
1193
- return;
1194
- }
1195
- axios
1196
- .post(this.apiUrl + "/" + this.modelName + "/sort", {
1197
- order: order,
1198
- })
1199
- .then((response) => {
1200
- let data = response.data;
1201
- this.toastSuccess("Orden Actualizado");
1202
- if (this.refreshAfterSave) this.refresh();
1203
- this.loading = false;
1204
- })
1205
- .catch((error) => {
1206
- //console.debug(error);
1207
- this.toastError(error);
1208
- this.loading = false;
1209
- });
1210
- }
1211
- },
1212
-
1213
- showExportModal() {
1214
- this.$refs["modal-export"].show();
1215
- },
1216
-
1217
-
1218
- exportItems() {
1219
- if (this.useVuexORM) {
1220
- return;
1221
- }
1222
-
1223
- if (!this.ajax) {
1224
- return;
1225
- }
1226
-
1227
- let exportItems = true;
1228
- let params;
1229
- let ids = this.selectedItems.map(it => it.id);
1230
- if (ids.length) {
1231
- params = { ids: ids, exportItems: exportItems, };
1232
- } else {
1233
- params = { filters: JSON.stringify(this.finalFilters), exportItems: exportItems, };
1234
- }
1235
- params.format = this.exportFormat;
1236
- this.loading = true;
1237
- axios
1238
- .get(this.apiUrl + "/" + this.modelName + "/export", { params: params, responseType: "blob", })
1239
- .then((response) => {
1240
- this.downloadBlobResponse(response);
1241
- this.loading = false;
1242
- })
1243
- .catch((error) => {
1244
- this.toastError(error);
1245
- this.loading = false;
1246
- });
1247
- },
1248
-
1249
-
1250
- showImportModal() {
1251
- this.$refs["modal-import"].show();
1252
- },
1253
- importItems() {
1254
- let formData = new FormData();
1255
- formData.append("file", this.fileImport);
1256
- axios
1257
- .post(this.apiUrl + "/" + this.modelName + "/import", formData, {
1258
- headers: {
1259
- "Content-Type": "multipart/form-data",
1260
- },
1261
- })
1262
- .then((response) => {
1263
- if (response && response.data && response.data.success == true) {
1264
- this.$refs["modal-import"].hide();
1265
- this.toastSuccess("Datos Importados con Éxito");
1266
- this.refresh();
1267
- } else {
1268
- this.toastError("No se pudo importar los datos.");
1269
- }
1270
- })
1271
- .catch((error) => {
1272
- console.error(error);
1273
- this.toastError(error);
1274
-
1275
- });
1276
- },
1277
-
1278
-
1279
- getArrayValue(value, displayProp, options = []) {
1280
- if (!Array.isArray(value)) return "N/A";
1281
- let values = [];
1282
- let valuesFinal = [];
1283
-
1284
- if (value.length > 0) {
1285
- if (typeof value[0] === "object" && displayProp) {
1286
- values = value.map((vv) => vv[displayProp]);
1287
- } else {
1288
- values = value.join(",");
1289
- }
1290
- } else {
1291
- return "";
1292
- }
1293
-
1294
- values.forEach(val => {
1295
- valuesFinal.push(this.getStateValue(val, options));
1296
- })
1297
-
1298
- return values.join(",");
1299
- },
1300
- getStateValue(value, options) {
1301
- if (!options) {
1302
- console.debug(
1303
- "State Column Not hast options returning value",
1304
- value,
1305
- options
1306
- );
1307
- return value;
1308
- }
1309
- let ops = options.filter((option) => {
1310
- if (Array.isArray(value)) {
1311
- return value.includes(option.id);
1312
- } else {
1313
- return option.id == value;
1314
- }
1315
- });
1316
- ops = ops.map((option) => {
1317
- return option.text ? option.text : option.label ? option.label : "";
1318
- });
1319
- return ops.join(", ");
1320
- },
1321
- async saveItemVuex(event = null) {
1322
- console.debug("save item 1", this.item);
1323
- let result;
1324
- let create = false;
1325
-
1326
-
1327
- if (this.vuexLocalforage) {
1328
-
1329
- if (this.markDirty) {
1330
- this.item.dirty = true;
1331
- }
1332
-
1333
- if (this.item.id) {
1334
-
1335
- result = await this.model.$create({ data: this.item });
1336
- console.debug("save item 4", this.item, result);
1337
- create = false;
1338
- } else {
1339
-
1340
- result = await this.model.$create({ data: this.item });
1341
- console.debug("save item 5", this.item, result);
1342
- create = true;
1343
- }
1344
-
1345
- } else {
1346
-
1347
- let jsondata = this.item.$toJson();
1348
- console.debug("save item 2", this.item, jsondata);
1349
- if (this.item.id) {
1350
- result = await this.model.api().put(this.apiUrl + "/" + this.modelName + '/' + this.item.id, jsondata);
1351
- create = false;
1352
- } else {
1353
- result = await this.model.api().post(this.apiUrl + "/" + this.modelName, jsondata);
1354
- create = true;
1355
- }
1356
-
1357
-
1358
- let responseStatus = result.response.status;
1359
- if (result.response.data.error) {
1360
- this.toastError(result.response.data.error);
1361
- this.loading = false;
1362
- return;
1363
- //throw new Error('Something is wrong.')
1364
- }
1365
-
1366
- result.save();
1367
- }
1368
-
1369
-
1370
- if (this.refreshAfterSave) this.refresh();
1371
- this.loading = false;
1372
- this.toastSuccess("Elemento Modificado");
1373
-
1374
- if (this.hideModalAfterSave || ((create && this.hideModalAfterCreate) || (!create && this.hideModalAfterUpdate))) {
1375
- this.$bvModal.hide("modal-form-item-" + this.modelName);
1376
- }
1377
-
1378
- },
1379
-
1380
- async saveItemLocal(event = null) {
1381
-
1382
- const itemSave = JSON.parse(JSON.stringify(this.item));
1383
- if (this.item.id || this.item.index) {
1384
-
1385
- let itemIndex;
1386
-
1387
- if (this.item.id) {
1388
- itemIndex = this.items.findIndex(
1389
- (item) => item.id == this.item.id
1390
- );
1391
- } else {
1392
-
1393
- itemIndex = this.items.findIndex(
1394
- (item) => item.index == this.item.index
1395
- );
1396
- }
1397
-
1398
- this.items[itemIndex] = itemSave;
1399
- if (this.hideModalAfterSave || this.hideModalAfterUpdate) {
1400
- this.$bvModal.hide("modal-form-item-" + this.modelName);
1401
- }
1402
- } else {
1403
-
1404
- itemSave.index = this.items.length + 1;
1405
- this.items.push(itemSave);
1406
- if (this.hideModalAfterSave || this.hideModalAfterCreate) {
1407
- this.$bvModal.hide("modal-form-item-" + this.modelName);
1408
- }
1409
- }
1410
- this.toastSuccess("Elemento Modificado");
1411
- this.loading = false;
1412
-
1413
- },
1414
- async loadOptions() {
1415
- for (let i = 0; i < this.columns.length; i++) {
1416
- const column = this.columns[i];
1417
-
1418
- if (column.options instanceof Promise) {
1419
- // Si las opciones son una función (promesa), esperar y actualizar
1420
- const options = await column.options;
1421
- this.$set(this.columns, i, { ...column, options });
1422
-
1423
- console.debug("Options promise", this.columns);
1424
- }
1425
- }
1426
-
1427
- this.optionsLoaded = true;
1428
- },
1429
- async saveItem(event = null) {
1430
- this.loading = true;
1431
- if (this.validate) {
1432
- let validation_result = true;
1433
- let validation_error_message = this.messageDefaultValidationError;
1434
- if (!validation_result) {
1435
- this.toastError(validation_error_message);
1436
- return;
1437
- }
1438
- } else {
1439
- if (event) event.preventDefault();
1440
- }
1441
-
1442
- if (this.useVuexORM) {
1443
- return this.saveItemVuex(event);
1444
- }
1445
-
1446
- if (!this.ajax) {
1447
- return this.saveItemLocal(event);
1448
- }
1449
- if (this.item.id) {
1450
- axios
1451
- .put(
1452
- this.apiUrl + "/" + this.modelName + "/" + this.item.id,
1453
- this.item
1454
- )
1455
- .then((response) => {
1456
- if (this.hideModalAfterSave || this.hideModalAfterUpdate) {
1457
- this.$bvModal.hide("modal-form-item-" + this.modelName);
1458
- }
1459
- let itemSv = response.data;
1460
- let itemIndex = this.items.findIndex(
1461
- (item) => item.id == this.item.id
1462
- );
1463
- this.items[itemIndex] = itemSv;
1464
- this.item = itemSv;
1465
- this.loading = false;
1466
- if (this.refreshAfterSave) this.refresh();
1467
- this.toastSuccess("Elemento Modificado");
1468
- this.$emit("itemSaved", { item: this.item });
1469
- this.$emit("itemUpdated", { item: this.item });
1470
- })
1471
- .catch((error) => {
1472
- this.toastError(error);
1473
- this.loading = false;
1474
- });
1475
- } else {
1476
- if (this.createMultipart) {
1477
- const formData = new FormData();
1478
- Object.keys(this.item).forEach((key) => {
1479
- if (this.item[key][0] && this.item[key][0].name) {
1480
- let files = this.item[key];
1481
- for (var x = 0; x < files.length; x++) {
1482
- formData.append(
1483
- key + "[]",
1484
- this.item[key][x],
1485
- this.item[key][x].name
1486
- );
1487
- }
1488
- } else formData.append(key, this.item[key]);
1489
- });
1490
-
1491
- axios
1492
- .post(this.apiUrl + "/" + this.modelName, formData)
1493
- .then((response) => {
1494
- this.loading = false;
1495
- if (this.hideModalAfterSave || this.hideModalAfterCreate) {
1496
- this.$bvModal.hide("modal-form-item-" + this.modelName);
1497
- }
1498
- if (response.data.success) {
1499
- if (response.data.message) {
1500
- this.toastSuccess(response.data.message);
1501
- }
1502
- return;
1503
- }
1504
- let itemSv = response.data;
1505
- this.items.push(itemSv);
1506
- this.item = itemSv;
1507
- if (this.refreshAfterSave) this.refresh();
1508
- this.toastSuccess("Elemento Creado");
1509
- this.$emit("itemSaved", { item: this.item });
1510
- this.$emit("itemCreated", { item: this.item });
1511
- })
1512
- .catch((error) => {
1513
- this.toastError(error);
1514
- this.loading = false;
1515
- });
1516
- } else {
1517
- axios
1518
- .post(this.apiUrl + "/" + this.modelName, this.item)
1519
- .then((response) => {
1520
- this.loading = false;
1521
- if (this.hideModalAfterSave || this.hideModalAfterUpdate) {
1522
- this.$bvModal.hide("modal-form-item-" + this.modelName);
1523
- }
1524
- if (response.data.success) {
1525
- if (response.data.message) {
1526
- this.toastSuccess(response.data.message);
1527
- }
1528
- return;
1529
- }
1530
-
1531
- let itemSv = response.data;
1532
- this.items.push(itemSv);
1533
- this.item = itemSv;
1534
- if (this.refreshAfterSave) this.refresh();
1535
- this.toastSuccess("Elemento Creado");
1536
- this.$emit("itemSaved", { item: this.item });
1537
- this.$emit("itemCreated", { item: this.item });
1538
- })
1539
- .catch((error) => {
1540
- this.toastError(error);
1541
- this.loading = false;
1542
- });
1543
- }
1544
- }
1545
- if (event) event.preventDefault();
1546
- },
1547
- clearItems() {
1548
- this.items = [];
1549
- },
1550
-
1551
- toastError(error) {
1552
- let error_message = "Ha ocurrido un error";
1553
-
1554
- if (typeof error === "string") {
1555
- error_message = error;
1556
- } else if (error.response) {
1557
- // handle API errors
1558
- if (error.response.status === 401) {
1559
- error_message = "No estás autorizado para realizar esta acción";
1560
- } else if (error.response.status === 404) {
1561
- error_message = "El recurso solicitado no se encontró";
1562
- } else if (error.response.status >= 400 && error.response.status < 500) {
1563
- error_message = "Hubo un problema con la solicitud realizada";
1564
- } else if (error.response.status >= 500) {
1565
- error_message = "El servidor no pudo procesar la solicitud";
1566
- }
1567
-
1568
- if (error.response.data) {
1569
- if (typeof error.response.data === "object") {
1570
- if (error.response.data.errors) {
1571
- let errors = error.response.data.errors;
1572
- this.responseErrors = errors;
1573
- error_message = Object.values(errors)[0][0];
1574
- } else if (error.response.data.message) {
1575
- error_message = error.response.data.message;
1576
- }
1577
- } else if (typeof error.response.data === "string") {
1578
- error_message = error.response.data;
1579
- }
1580
- }
1581
- } else if (error.request) {
1582
- // handle network errors
1583
- error_message = "No se pudo conectar con el servidor. Verifique su conexión a Internet.";
1584
- } else if (error.message) {
1585
- // handle other errors
1586
- error_message = error.message;
1587
- }
1588
-
1589
-
1590
-
1591
- this.$bvToast.toast(error_message, {
1592
- title: `Error`,
1593
- toaster: "b-toaster-bottom-right",
1594
- variant: "danger",
1595
- solid: true,
1596
- appendToast: true,
1597
- });
1598
- },
1599
- toastSuccess(message) {
1600
- this.$bvToast.toast(message, {
1601
- title: `Listo`,
1602
- toaster: "b-toaster-bottom-right",
1603
- variant: "success",
1604
- solid: true,
1605
- appendToast: true,
1606
- });
1607
- },
1608
- downloadBlobResponse(response, extension = null) {
1609
- const url = window.URL.createObjectURL(new Blob([response.data]));
1610
- const link = document.createElement("a");
1611
- link.href = url;
1612
-
1613
- let contentdisposition = response.headers['content-disposition'];
1614
- let filename = "Export";
1615
-
1616
-
1617
- if (contentdisposition) {
1618
- filename = contentdisposition.split('filename=')[1].split('.')[0];
1619
- filename = filename.replace('_', '');
1620
- filename = filename.replace('"', '');
1621
- extension = contentdisposition.split('.')[1].split(';')[0];
1622
- extension = extension.replace('_', '');
1623
- extension = extension.replace('"', '');
1624
- }
1625
-
1626
-
1627
- console.debug("DOWNLOAD ", filename, extension);
1628
- link.setAttribute("download", filename + '.' + extension);
1629
- document.body.appendChild(link);
1630
- link.click();
1631
- },
1632
- onChangeFilter(event) {
1633
- this.forceRecomputeCounter++;
1634
- console.debug("Filters debug ", this.finalFilters, this.internalFilter, this.internalFilters, this.filter, this.filters);
1635
- setTimeout(() => {
1636
- this.refresh();
1637
- }, 1);
1638
- },
1639
- onPaginationChange(page) {
1640
- this.fetchItems(page);
1641
- },
1642
- makePagination: function (data) {
1643
- let pagination = {
1644
- current_page: data.current_page,
1645
- last_page: data.last_page,
1646
- next_page_url: data.next_page_url,
1647
- prev_page_url: data.prev_page_url,
1648
- total: data.total,
1649
- per_page: data.per_page,
1650
- };
1651
- this.pagination = pagination;
1652
- },
1653
- },
1654
-
1655
- beforeDestroy() {
1656
- // Eliminar el oyente de eventos al destruir el componente para evitar pérdidas de memoria
1657
- window.removeEventListener("resize", this.handleResize);
1658
- },
1659
523
  };
1660
524
  </script>
1661
525
 
1662
526
  <template>
1663
527
  <div class="crud">
1664
- <div class="crud-header" v-if="showHeader">
1665
- <h4 class="crud-title" v-if="showTitle">{{ title }}</h4>
1666
- <b-sidebar v-model="filterSidebarOpen" title="Filtrar" right shadow>
1667
- <slot name="sidebarFilters" v-bind:createItem="createItem" v-bind:toggleDisplayMode="toggleDisplayMode"
1668
- v-bind:loading="loading" v-bind:isColumnHasFilter="isColumnHasFilter" v-bind:setFilter="setFilter">
1669
- <div class="px-3 py-2">
1670
- <div v-for="(column, indexc) in columns" :key="indexc">
1671
- <div v-if="isColumnHasFilter(column)">
1672
- <slot :name="'sidebar-filter-' + column.prop" v-bind:column="column" v-bind:filter="filter"
1673
- v-bind:internalFilterByProp="internalFilterByProp" v-if="internalFilterByProp(column.prop)">
1674
- <div class="form-group" v-if="column.type == 'boolean'">
1675
- <label>{{ column.label }}</label>
1676
-
1677
- <select class="form-control" v-model="internalFilterByProp(column.prop).value"
1678
- @change="onChangeFilter($event)">
1679
- <option value=""></option>
1680
- <option value="1">Sí</option>
1681
- <option value="0">No</option>
1682
- </select>
1683
- </div>
1684
- <div class="form-group" v-else-if="column.type == 'date'">
1685
- <div class="row">
1686
- <div class="col-6">
1687
- <b-form-datepicker v-model="internalFilterByProp(column.prop + '_from').value
1688
- " today-button reset-button close-button locale="es"></b-form-datepicker>
1689
- </div>
1690
- <div class="col-6">
1691
- <b-form-datepicker v-model="internalFilterByProp(column.prop + '_to').value
1692
- " today-button reset-button close-button locale="es"></b-form-datepicker>
1693
- </div>
1694
- </div>
1695
- </div>
1696
-
1697
- <div class="form-group" v-else-if="column.type == 'state'">
1698
- <label>{{ column.label }}</label>
1699
-
1700
- <select class="form-control" v-model="internalFilterByProp(column.prop).value"
1701
- @change="onChangeFilter($event)" v-if="optionsLoaded">
1702
- <option value=""></option>
1703
- <option :value="option.id ? option.id : option.value" v-for="option in column.options"
1704
- :key="option.id ? option.id : option.value">
1705
- {{
1706
- option.text
1707
- ? option.text
1708
- : option.label
1709
- ? option.label
1710
- : ""
1711
- }}
1712
- </option>
1713
- </select>
1714
- </div>
1715
-
1716
- <div class="form-group" v-else-if="column.type == 'array'">
1717
- <label>{{ column.label }}</label>
1718
-
1719
- <select class="form-control" v-model="internalFilterByProp(column.prop).value"
1720
- @change="onChangeFilter($event)" v-if="optionsLoaded">
1721
- <option value=""></option>
1722
- <template v-if="column.options">
1723
- <option :value="option.id ? option.id : option.value" v-for="option in column.options"
1724
- :key="option.id ? option.id : option.value">
1725
- {{
1726
- option.text
1727
- ? option.text
1728
- : option.label
1729
- ? option.label
1730
- : ""
1731
- }}
1732
- </option>
1733
- </template>
1734
- </select>
1735
- </div>
1736
-
1737
-
1738
- <div class="form-group" v-else>
1739
- <label>{{ column.label }}</label>
1740
-
1741
- <input class="form-control" v-model.lazy="internalFilterByProp(column.prop).value"
1742
- @change="onChangeFilter($event)" />
1743
- </div>
1744
- </slot>
1745
- </div>
1746
- </div>
1747
-
1748
- <div class="mt-3 d-flex justify-content-center">
1749
- <button class="btn btn-light" @click="resetFilters()">
1750
- Reset
1751
- </button>
1752
- <button class="btn btn-info" @click="onChangeFilter($event)">
1753
- Filtrar
1754
- </button>
1755
- </div>
1756
- </div>
1757
- </slot>
1758
- </b-sidebar>
1759
-
1760
- <div class="table-options">
1761
- <b-button-group class="mr-1">
1762
- <slot name="tableActions" v-bind:createItem="createItem" v-bind:toggleDisplayMode="toggleDisplayMode"
1763
- v-bind:loading="loading">
1764
- <slot name="tableActionsPrepend" v-bind:loading="loading"> </slot>
1765
-
1766
- <b-button variant="info" @click="showImportModal()" v-if="showImport">
1767
- <b-icon-cloud-upload></b-icon-cloud-upload>{{ messageImport }}
1768
- </b-button>
1769
- <b-button variant="info" @click="showExportModal()" v-if="showExport">
1770
- <b-icon-cloud-download></b-icon-cloud-download>{{ messageExport }}
1771
- </b-button>
1772
- <b-button variant="info" v-if="showPrincipalSortBtn" @click="togglePrincipalSort()" :disabled="loading">
1773
- <b-icon-sort-numeric-down v-if="principalSort"></b-icon-sort-numeric-down>
1774
- <b-icon-sort-numeric-up v-else></b-icon-sort-numeric-up>
1775
- </b-button>
1776
- <b-button variant="danger" @click="confirmBulkDelete()"
1777
- v-if="bulkDelete"><b-icon-trash></b-icon-trash></b-button>
1778
- <b-button variant="success" v-if="showCreateBtn" @click="createItem()" :disabled="loading">
1779
- <b-icon-plus></b-icon-plus>{{ messageNew }}
1780
- </b-button>
1781
- <b-button variant="info" v-if="enableFilters" @click="toggleFilters()">Filtros</b-button>
1782
- <b-button variant="info" @click="refresh()"><b-icon-arrow-clockwise></b-icon-arrow-clockwise></b-button>
1783
- <b-button variant="info" @click="toggleDisplayMode()" :disabled="loading" v-if="displayModeToggler">
1784
- <b-icon-card-list v-if="displayMode == displayModes.MODE_TABLE"></b-icon-card-list>
1785
- <b-icon-table v-else-if="displayMode == displayModes.MODE_CARDS"></b-icon-table>
1786
- </b-button>
1787
-
1788
- <div class="crud-search m-0" v-if="showSearch">
1789
- <b-input-group>
1790
- <b-input-group-prepend>
1791
- <b-button variant="info" @click="displaySearch = !displaySearch"
1792
- :class="{ open: displaySearch }"><b-icon-search></b-icon-search></b-button>
1793
- </b-input-group-prepend>
1794
- <b-form-input v-if="displaySearch" v-model="search" class="pl-2" type="search" required
1795
- :placeholder="searchPlaceholder" debounce="500"></b-form-input>
1796
- </b-input-group>
1797
-
1798
- <slot name="tableActionsAppend" v-bind:loading="loading"> </slot>
1799
- </div>
1800
- </slot>
1801
- </b-button-group>
1802
- </div>
1803
- </div>
1804
-
1805
- <div :class="['table-responsive', tableContainerClass]" v-if="displayMode == displayModes.MODE_TABLE">
1806
- <table :class="['table table-hover table-striped w-100', tableClass]">
1807
- <thead class="thead-light">
1808
- <tr>
1809
- <slot name="rowHead">
1810
- <th v-for="(column, indexc) in columns" :key="indexc"
1811
- :style="{ width: column.width ? column.width : 'inherit' }" scope="col">
1812
- <slot :name="'filter-' + column.prop" v-bind:column="column" v-bind:filter="filter"
1813
- v-bind:internalFilterByProp="internalFilterByProp" v-if="enableFilters &&
1814
- filtersVisible &&
1815
- isColumnHasFilter(column) &&
1816
- internalFilterByProp(column.prop)
1817
- ">
1818
-
1819
- <div class="form-group">
1820
- <select v-if="column.type == 'boolean'" class="form-control form-control-md p-2"
1821
- v-model="internalFilterByProp(column.prop).value" @change="onChangeFilter($event)">
1822
- <option value="">{{ column.label }}</option>
1823
- <option value="1">Sí</option>
1824
- <option value="0">No</option>
1825
- </select>
1826
-
1827
- <div class="row" v-else-if="column.type == 'date'">
1828
- <div class="col-6">
1829
- <b-form-datepicker v-model="internalFilterByProp(column.prop + '_from').value
1830
- " today-button reset-button close-button locale="es"
1831
- class="form-control-md p-2"></b-form-datepicker>
1832
- </div>
1833
- <div class="col-6">
1834
- <b-form-datepicker v-model="internalFilterByProp(column.prop + '_to').value
1835
- " today-button reset-button close-button locale="es"
1836
- class="form-control-md p-2"></b-form-datepicker>
1837
- </div>
1838
- </div>
1839
-
1840
- <select v-else-if="column.type == 'state' && optionsLoaded" class="form-control form-control-md p-2"
1841
- v-model="internalFilterByProp(column.prop).value" @change="onChangeFilter($event)"
1842
- :placeholder="column.label">
1843
- <option value="">{{ column.label }}</option>
1844
- <option :value="option.id" v-for="(option, indexo) in column.options" :key="indexo">
1845
- {{
1846
- option.text
1847
- ? option.text
1848
- : option.label
1849
- ? option.label
1850
- : ""
1851
- }}
1852
- </option>
1853
- </select>
1854
-
1855
- <select v-else-if="column.type == 'array' && optionsLoaded" class="form-control form-control-md p-2"
1856
- v-model="internalFilterByProp(column.prop).value" @change="onChangeFilter($event)"
1857
- :placeholder="column.label">
1858
- <option value="">{{ column.label }}</option>
1859
- <option :value="option.id" v-for="(option, indexo) in column.options" :key="indexo">
1860
- {{
1861
- option.text
1862
- ? option.text
1863
- : option.label
1864
- ? option.label
1865
- : ""
1866
- }}
1867
- </option>
1868
- </select>
1869
-
1870
- <b-form-checkbox v-else-if="column.type == 'checkbox'" name="select-all"
1871
- @change="toggleAll($event)">
1872
- </b-form-checkbox>
1873
-
1874
- <b-form-checkbox v-else-if="column.type == 'select'" name="select-all" @change="toggleAll($event)">
1875
- </b-form-checkbox>
1876
-
1877
- <input v-else class="form-control form-control-md p-2"
1878
- v-model="internalFilterByProp(column.prop).value" :placeholder="column.label"
1879
- @change="onChangeFilter($event)" />
1880
-
1881
- </div>
1882
- </slot>
1883
- <span v-else-if="column.type == 'select'">
1884
- <b-form-checkbox name="select-all" @change="toggleAll($event)"></b-form-checkbox>
1885
- </span>
1886
- <span v-else>{{ column.label }}</span>
1887
-
1888
-
1889
- <span
1890
- v-if="sortable && column.type != 'select' && column.type != 'checkbox' && internalFilterByProp(column.prop + '_sort')"
1891
- class="sort-filter" @click="toggleSortFilter(column)"><b-icon-sort-down
1892
- v-if="!internalFilterByProp(column.prop + '_sort').value"></b-icon-sort-down><b-icon-sort-up
1893
- v-if="internalFilterByProp(column.prop + '_sort').value == 'ASC'"></b-icon-sort-up>
1894
- <b-icon-sort-down
1895
- v-if="internalFilterByProp(column.prop + '_sort').value == 'DESC'"></b-icon-sort-down>
1896
- </span>
1897
- </th>
1898
- </slot>
1899
- </tr>
1900
- </thead>
1901
-
1902
- <draggable v-model="items" :group="draggableGroup" tag="tbody" :draggable="orderable ? '.item' : '.none'"
1903
- @start="drag = true" @end="drag = false" @sort="onSort()" @add="onDraggableAdded($event)"
1904
- @change="onDraggableChange($event)" :options="draggableOptions">
1905
- <tr v-for="(item, index) in itemsList" v-bind:key="index" @mouseover="onRowHover(item, index)"
1906
- @click="onRowClick(item, index)" class="item">
1907
-
1908
- <th :colspan="columns.length" v-if="grouped && item.crudgroup">
1909
- <span>{{ item.crudgrouplabel }}</span>
1910
- </th>
1911
-
1912
- <slot name="row" v-bind:item="item" v-else>
1913
- <td v-for="(column, indexc) in columns" :key="indexc" :scope="column.prop == 'id' ? 'row' : ''">
1914
- <slot :name="'cell-' + column.prop" v-bind:item="item" v-bind:index="index" v-bind:itemindex="index"
1915
- v-bind:columnindex="indexc">
1916
- <span v-if="column.type == 'boolean'">
1917
- <b-badge variant="success" v-if="itemValue(column, item) == 'true' ||
1918
- itemValue(column, item) == 1 ||
1919
- itemValue(column, item) == '1'
1920
- "><b-icon-check-circle></b-icon-check-circle></b-badge>
1921
- <b-badge variant="danger" v-if="!itemValue(column, item) ||
1922
- itemValue(column, item) == '0' ||
1923
- itemValue(column, item) == 'false'
1924
- "><b-icon-x-circle></b-icon-x-circle></b-badge>
1925
- </span>
1926
- <span v-else-if="column.type == 'date'">
1927
- {{
1928
- itemValue(column, item)
1929
- ? moment(itemValue(column, item)).format(
1930
- column.format ? column.format : 'L LT'
1931
- )
1932
- : itemValue(column, item)
1933
- }}
1934
- </span>
1935
- <span v-else-if="column.type == 'select'">
1936
- <b-form-checkbox v-model="item.selected" @change="onCheckSelect($event, item)">
1937
- </b-form-checkbox>
1938
- </span>
1939
- <span v-else-if="column.type == 'state' && optionsLoaded">
1940
- {{
1941
- getStateValue(itemValue(column, item), column.options)
1942
- }}
1943
- </span>
1944
- <span v-else-if="column.type == 'array' && optionsLoaded">
1945
- {{
1946
- getArrayValue(
1947
- itemValue(column, item),
1948
- column.displayProp,
1949
- column.options
1950
- )
1951
- }}
1952
- </span>
1953
- <span v-else>
1954
- {{ itemValue(column, item) }}
1955
- </span>
1956
- </slot>
1957
-
1958
- <b-button-group v-if="column.type == 'actions'">
1959
- <slot name="rowAction" v-bind:item="item" v-bind:index="index" v-bind:showItem="showItem"
1960
- v-bind:updateItem="updateItem" v-bind:removeItem="removeItem">
1961
- <b-button variant="primary" @click="showItem(item.id, index)">
1962
- <b-icon-eye></b-icon-eye>
1963
- </b-button>
1964
- <b-button variant="secondary" @click="updateItem(item.id, index)">
1965
- <b-icon-pencil></b-icon-pencil>
1966
- </b-button>
1967
- <b-button variant="danger" @click="removeItem(item.id, index)">
1968
- <b-icon-trash></b-icon-trash>
1969
- </b-button>
1970
- </slot>
1971
- </b-button-group>
1972
- </td>
1973
- </slot>
1974
-
1975
- </tr>
1976
-
1977
- </draggable>
1978
-
1979
- </table>
1980
- <p v-if="!loading && items && items.length == 0 && !infiniteScroll" class="p-3">
1981
- {{ messageEmptyResults }}
1982
- </p>
1983
- </div>
1984
-
1985
- <div v-else-if="displayMode == displayModes.MODE_CARDS">
1986
- <draggable v-model="items" :group="draggableGroup" :draggable="orderable ? '.item' : '.none'" @start="drag = true"
1987
- @end="drag = false" @sort="onSort()" @add="onDraggableAdded($event)" @change="onDraggableChange($event)"
1988
- :options="draggableOptions">
1989
- <masonry
1990
- :cols="{ default: 12 / colLg, 1400: 12 / colXl, 1200: 12 / colLg, 1000: 12 / colMd, 700: 12 / colSm, 400: 12 / colXs }"
1991
- :gutter="{ default: '15px', 700: '15px' }">
1992
- <div v-for="(item, itemIndex) in itemsList" v-bind:key="itemIndex" class="item">
1993
- <slot name="card" v-bind:item="item">
1994
- <ItemCard :item="item" :columns="columns" :index="itemIndex"
1995
- :cardClass="cardClass" :cardHideFooter="cardHideFooter" :itemValue="itemValue"
1996
- :getStateValue="getStateValue" :getArrayValue="getArrayValue" :showItem="showItem"
1997
- :updateItem="updateItem" :removeItem="removeItem" />
1998
- </slot>
1999
- </div>
2000
- </masonry>
2001
- </draggable>
2002
-
2003
- <p v-if="!loading && items && items.length == 0 && !infiniteScroll" class="p-3">
2004
- {{ messageEmptyResults }}
2005
- </p>
2006
-
2007
- </div>
2008
-
2009
- <div v-else-if="displayMode == displayModes.MODE_KANBAN">
2010
- {{ JSON.stringify(items) }}
2011
-
2012
- <div v-for="(column, colIndex) in items" :key="colIndex" class="kanban-column">
2013
- <div class="kanban-column-header">
2014
- {{ column.groupLabel }}
2015
- </div>
2016
-
2017
- {{ JSON.stringify(column) }}
2018
-
2019
-
2020
- <draggable v-model="column.items" group="kanban" class="kanban-column-body" @end="onDragEnd">
2021
-
2022
- <div v-for="(item, itemIndex) in column.items" v-bind:key="itemIndex" class="item">
2023
- <slot name="card" v-bind:item="item">
2024
- <ItemCard :key="itemIndex" :item="item" :columns="columns" :index="index"
2025
- :cardClass="cardClass" :cardHideFooter="cardHideFooter" :itemValue="itemValue"
2026
- :getStateValue="getStateValue" :getArrayValue="getArrayValue" :showItem="showItem"
2027
- :updateItem="updateItem" :removeItem="removeItem" />
2028
- </slot>
2029
- </div>
2030
-
2031
- </draggable>
2032
-
2033
- </div>
2034
-
2035
- </div>
2036
-
2037
- <div v-else-if="displayMode == displayModes.MODE_CUSTOM">
2038
- <div :class="listContainerClass">
2039
- <p v-if="!loading && items && items.length == 0 && !infiniteScroll" class="p-3">
2040
- {{ messageEmptyResults }}
2041
- </p>
2042
- <div :class="listItemClass" v-for="(item, index) in itemsList" v-bind:key="index">
2043
- <slot name="card" v-bind:item="item"> </slot>
2044
- </div>
2045
- </div>
2046
- </div>
528
+ <CrudHeader />
529
+
530
+ <CrudTable />
531
+ <CrudCards />
532
+ <CrudKanban />
533
+ <CrudCustom />
534
+
2047
535
  <b-overlay :show="loading" rounded="sm"></b-overlay>
2048
- <infinite-loading ref="infiniteLoading" @infinite="infiniteHandler" v-if="infiniteScroll"
2049
- :forceUseInfiniteWrapper="true" :key="infiniteScrollKey">
2050
- <div slot="spinner">
2051
- <div class="text-center">{{ messageLoading }}</div>
2052
- </div>
2053
- <div slot="no-more">
2054
- <div class="text-center" v-if="!loading">{{ messageNoMore }}</div>
2055
- </div>
2056
- <div slot="no-results">
2057
- <div class="text-center" v-if="!loading">{{ items.length == 0 ? messageEmptyResults : messageNoMore }}</div>
2058
- </div>
2059
- </infinite-loading>
2060
- <div class="paginator-data" v-if="!infiniteScroll">
2061
- Filas: {{ pagination.total }} | xPág: {{ pagination.per_page }} | Pág: {{ pagination.current_page }} |
2062
- Seleccionados:
2063
- {{
2064
- selectedItems.length }}
2065
- </div>
2066
- <div class="crud-paginator" v-if="!infiniteScroll">
2067
- <b-pagination v-if="showPaginator" v-model="pagination.current_page" :total-rows="pagination.total"
2068
- :per-page="pagination.per_page" @change="onPaginationChange($event)"></b-pagination>
2069
- </div>
2070
- <b-modal :id="'modal-form-item-' + modelName" hide-footer size="xl" :title="title" no-close-on-backdrop>
2071
- <b-overlay :show="loading" rounded="sm">
2072
- <template v-if="validate">
2073
- <form @submit="saveItem">
2074
- <slot name="form" v-bind:item="item" v-if="item">
2075
- <b-form-group label="Nombre:" description="Nombre ">
2076
- <b-form-input v-model="item.title" type="text" required placeholder="Nombre"></b-form-input>
2077
- </b-form-group>
2078
- </slot>
2079
- <b-button block type="submit" variant="success" :disabled="loading">
2080
- <b-spinner small v-if="loading"></b-spinner>{{ messageSave }}
2081
- </b-button>
2082
- </form>
2083
- </template>
2084
- <template v-if="!validate">
2085
- <slot name="form" v-bind:item="item" v-if="item">
2086
- <b-form-group :label="key" v-for="(value, key) in item" :key="key">
2087
- <b-form-input v-model="item[key]" type="text" required></b-form-input>
2088
- </b-form-group>
2089
- </slot>
2090
- <b-button block type="submit" variant="success" :disabled="loading" @click="saveItem()">
2091
- <b-spinner small v-if="loading"></b-spinner>{{ messageSave }}
2092
- </b-button>
2093
- </template>
2094
- </b-overlay>
2095
- </b-modal>
2096
- <b-modal :id="'modal-show-item-' + modelName" hide-footer size="xl" :title="title" no-close-on-backdrop>
2097
- <slot name="show" v-bind:item="item" v-if="item">
2098
- <b-list-group>
2099
- <b-list-group-item v-for="(value, key) in item" :key="key">
2100
- <b-row class="w-100">
2101
- <b-col cols="4" class="font-weight-bold">{{ key }}</b-col>
2102
- <b-col cols="8">{{ JSON.stringify(value) }}</b-col>
2103
- </b-row>
2104
- </b-list-group-item>
2105
- </b-list-group>
2106
- </slot>
2107
- </b-modal>
2108
-
2109
-
2110
- <b-modal ref="modal-import" title="Importar" hide-footer v-if="showImport">
2111
- <slot name="import" v-bind:item="item" v-if="item">
2112
- <b-overlay :show="loading" rounded="sm">
2113
- <b-form-file v-model="fileImport" :state="Boolean(fileImport)" browse-text="Explorar"
2114
- placeholder="Importar..." drop-placeholder="Arrastrar Archivo aquí..."></b-form-file>
2115
- <div class="text-center mt-3">
2116
- <b-button variant="info" v-on:click="importItems()" :disabled="loading">
2117
- <b-icon-cloud-upload></b-icon-cloud-upload>
2118
- {{ loading ? "Cargando..." : "Importar" }}
2119
- </b-button>
2120
- </div>
2121
- </b-overlay>
2122
- </slot>
2123
- </b-modal>
2124
-
2125
- <b-modal ref="modal-export" title="Exportar" hide-footer v-if="showExport">
2126
- <slot name="export" v-bind:item="item" v-if="item">
2127
- <b-overlay :show="loading" rounded="sm">
2128
-
2129
- <p v-if="selectedItems.length">Se exportará {{ selectedItems.length }} elementos.</p>
2130
- <p v-else>Se exportará la consulta actual.</p>
2131
-
2132
- <select class="form-control" v-model="exportFormat">
2133
- <option value="JSON">JSON</option>
2134
- <option value="XLSX">XLSX</option>
2135
- </select>
2136
-
2137
- <div class="text-center mt-3">
2138
- <b-button variant="info" v-on:click="exportItems()" :disabled="loading">
2139
- <b-icon-cloud-upload></b-icon-cloud-upload>
2140
- {{ loading ? "Cargando..." : "Exportar" }}
2141
- </b-button>
2142
- </div>
2143
- </b-overlay>
2144
- </slot>
2145
- </b-modal>
536
+
537
+ <CrudPagination />
538
+ <CrudModals />
2146
539
  </div>
2147
540
  </template>
2148
541
 
@@ -2153,6 +546,38 @@ tr td:first-child {
2153
546
  white-space: nowrap;
2154
547
  }
2155
548
 
549
+ tbody tr.selected {
550
+ background-color: #e3f2fd !important;
551
+
552
+ td {
553
+ background-color: transparent !important;
554
+ }
555
+
556
+ &:hover {
557
+ background-color: #bbdefb !important;
558
+
559
+ td {
560
+ background-color: transparent !important;
561
+ }
562
+ }
563
+ }
564
+
565
+ .table-striped tbody tr.selected:nth-of-type(odd) {
566
+ background-color: #e3f2fd !important;
567
+
568
+ td {
569
+ background-color: transparent !important;
570
+ }
571
+ }
572
+
573
+ .table-striped tbody tr.selected:nth-of-type(even) {
574
+ background-color: #e3f2fd !important;
575
+
576
+ td {
577
+ background-color: transparent !important;
578
+ }
579
+ }
580
+
2156
581
  .crud-pagination {
2157
582
  display: flex;
2158
583
  align-items: center;