innoboxrr-vue-datatable 1.3.4 → 2.0.0

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.
package/src/DataTable.vue CHANGED
@@ -1,389 +1,426 @@
1
- <template>
2
- <div>
3
- <div v-if="showTopbar">
4
- <div class="uk-container uk-container-expand pt-4">
5
- <div uk-grid>
6
- <div class="uk-width-expand" v-if="hasActions">
7
- <button
8
- class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 mr-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
9
- @click="actionButtonClicked(crudActions)">
10
- Acciones
11
- </button>
12
- <nav-dropdown-component id="actionCrudDropdown" pos="right">
13
- <li v-for="action in crudActions" :key="action.id">
14
- <template v-if="action.route">
15
- <icon-route-component
16
- v-if="action.policy"
17
- :name="action.params.to.name"
18
- :params="{...action.params.to.params, ...extraParams}"
19
- :query="action.params.to.query ? {...action.params.to.query, ...extraQuery} : {...extraQuery}"
20
- :icon="action.icon"
21
- :text="action.name" />
22
- <disabled-link-component
23
- v-else
24
- :icon="action.icon"
25
- :text="action.name"/>
26
- </template>
27
- <template v-else>
28
- <icon-link-component
29
- v-if="action.policy"
30
- :icon="action.icon"
31
- :text="action.name"
32
- @click="actionClicked(action) , closeDropdown($event)" />
33
- <disabled-link-component
34
- v-else
35
- :icon="action.icon"
36
- :text="action.name"/>
37
- </template>
38
- </li>
39
- </nav-dropdown-component>
40
- </div>
41
- <div v-else>
42
- <div class="uk-width-expand"></div>
43
- </div>
44
- <div v-if="hasFilter" class="uk-width-auto">
45
- <div class="uk-grid-divider uk-child-width-expand uk-text-center" uk-grid>
46
- <div>
47
- <span
48
- class="uk-text-right pointer"
49
- :uk-tooltip="`title: ${'Update results'}`"
50
- @click="updateFilters">
51
- <svg class="w-6 h-6 text-slate-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 20">
52
- <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 1v5h-5M2 19v-5h5m10-4a8 8 0 0 1-14.947 3.97M1 10a8 8 0 0 1 14.947-3.97"/>
53
- </svg>
54
- </span>
55
- </div>
56
- <div>
57
- <span
58
- class="uk-text-right pointer"
59
- uk-toggle="target: .filter-form; animation: uk-animation-scale-up;"
60
- uk-tooltip="title: Buscar">
61
- <svg class="w-6 h-6 text-slate-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 18">
62
- <path d="M18.85 1.1A1.99 1.99 0 0 0 17.063 0H2.937a2 2 0 0 0-1.566 3.242L6.99 9.868 7 14a1 1 0 0 0 .4.8l4 3A1 1 0 0 0 13 17l.01-7.134 5.66-6.676a1.99 1.99 0 0 0 .18-2.09Z"/>
63
- </svg>
64
- </span>
65
- </div>
66
- </div>
67
- </div>
68
- </div>
69
- </div>
70
- <div
71
- v-if="hasFilter"
72
- class="filter-form uk-card uk-card-body uk-padding-remove-top"
73
- hidden>
74
- <slot name="filterForm"></slot>
75
- </div>
76
- </div>
77
- <div
78
- class="uk-container uk-container-expand"
79
- :class="{
80
- 'ptb-20': showTopbar
81
- }">
82
- <div
83
- class="uk-padding-small"
84
- :class="{
85
- 'bg-white p-6 rounded-lg shadow dark:border-slate-700 dark:bg-slate-800': cardWrapper
86
- }">
87
- <data-table-component
88
- :actions="hasActions"
89
- :data-table="dataTable"
90
- :extra-params="extraParams"
91
- :extra-query="extraQuery"
92
- :show-table-header="showTableHeader"
93
- :data-table-components="dataTableComponents"
94
- @sortColumn="sortColumn"
95
- @actionButtonClicked="actionButtonClicked"
96
- @actionClicked="actionClicked" />
97
- <select-pagination-component
98
- :meta="pagination.meta"
99
- :links="pagination.links"
100
- @updatePage="updatePage" />
101
- </div>
102
- </div>
103
- </div>
104
- </template>
105
-
106
- <script>
107
-
108
- import { markRaw } from 'vue';
109
- import NavDropdownComponent from './components/NavDropdownComponent.vue'
110
- import IconRouteComponent from './components/IconRouteComponent.vue'
111
- import IconLinkComponent from './components/IconLinkComponent.vue'
112
- import DisabledLinkComponent from './components/DisabledLinkComponent.vue'
113
- import DataTableComponent from './components/DataTableComponent.vue'
114
- import SelectPaginationComponent from './components/SelectPaginationComponent.vue'
115
-
116
- export default {
117
-
118
- components: {
119
- NavDropdownComponent,
120
- IconRouteComponent,
121
- IconLinkComponent,
122
- DisabledLinkComponent,
123
- DataTableComponent,
124
- SelectPaginationComponent
125
- },
126
- props: {
127
- dataUrl: {
128
- type: String,
129
- required: true
130
- },
131
- dataMethod: {
132
- type: String,
133
- default: 'post',
134
- },
135
- model: {
136
- type: Object,
137
- required: true
138
- },
139
- policyUrl: {
140
- type: String,
141
- required: true
142
- },
143
- policyMethod: {
144
- type: String,
145
- default: 'post'
146
- },
147
- showTopbar:{
148
- type: Boolean,
149
- default: true
150
- },
151
- hasActions: {
152
- type: Boolean,
153
- default: true,
154
- },
155
- hasFilter: {
156
- type: Boolean,
157
- default: true,
158
- },
159
- formFilters: {
160
- type: Object,
161
- default: {}
162
- },
163
- externalFilters: {
164
- type: Object,
165
- default: {}
166
- },
167
- extraParams: {
168
- type: Object,
169
- default: {}
170
- },
171
- extraQuery: {
172
- type: Object,
173
- default: {}
174
- },
175
- hideColumns: {
176
- type: Array,
177
- default: []
178
- },
179
- cardWrapper: {
180
- type: Boolean,
181
- default: true
182
- },
183
- showTableHeader: {
184
- type: Boolean,
185
- default: true,
186
- }
187
- },
188
- mounted() {
189
- this.fetchData();
190
- },
191
- data() {
192
- return {
193
- crudActions: this.model.crudActions(),
194
- dataTable: {
195
- head: this.dataTableHead(),
196
- body: []
197
- },
198
- pagination: {
199
- meta: [],
200
- links: []
201
- },
202
- dataTableComponents: this.registerComponents(),
203
- sort: this.model.dataTableSort(),
204
- orderBy: 'id',
205
- internalSort: false,
206
- page: 1,
207
- fetchDataAttempts: 0,
208
- fetchPoliciesAttempts: 0,
209
- }
210
- },
211
- watch: {
212
- formFilters: {
213
- handler(val, oldVal) {
214
- this.page = 1;
215
- this.updateFilters();
216
- },
217
- deep: true
218
- },
219
- externalFilters: {
220
- handler(val, oldVal) {
221
- if(!_.isEqual(val, oldVal)) this.updateFilters();
222
- },
223
- deep: true
224
- }
225
- },
226
- methods: {
227
- dataTableHead() {
228
- // NOTA: En este punto puede existir una oportunidad para sobreescribir las columnas del datatable
229
- let cols = this.model.dataTableHead();
230
- let rm = this.hideColumns;
231
- for( let i = cols.length - 1; i >= 0; i--) {
232
- for( let j = 0; j < rm.length; j++) {
233
- if(cols[i] && (cols[i].id === rm[j].id)){
234
- cols.splice(i, 1);
235
- }
236
- }
237
- }
238
- return cols;
239
- },
240
- registerComponents() {
241
- if (this.model.dataTableComponents === undefined || typeof this.model.dataTableComponents !== 'function') {
242
- return {};
243
- }
244
-
245
- // Usar `markRaw` para cada componente
246
- let components = Object.keys(this.model.dataTableComponents())
247
- .reduce((acc, key) => {
248
- acc[key] = markRaw(this.model.dataTableComponents()[key]);
249
- return acc;
250
- }, {});
251
- return components;
252
- },
253
- fetchData() {
254
- const requestData = {
255
- method: this.dataMethod,
256
- url: this.dataUrl,
257
- data: this.dataMethod === 'post' ? this.getFilters() : null,
258
- params: this.dataMethod === 'get' ? this.getFilters() : null
259
- };
260
- axios(requestData).then(res => {
261
- this.fetchDataAttempts = 0;
262
- this.dataTable.body = res.data.data;
263
- this.pagination.meta = res.data.meta;
264
- this.pagination.links = res.data.links;
265
- }).catch(error => {
266
- if (error.response.status === 403) {
267
- // this.$router.push({name: "NotAuthorized" });
268
- } else {
269
- if (this.fetchDataAttempts <= 3) {
270
- setTimeout(() => {
271
- ++this.fetchDataAttempts;
272
- this.fetchData();
273
- }, 1500);
274
- }
275
- }
276
- });
277
- },
278
- getFilters() {
279
- let formFilters = this.formFilters;
280
- let params = {
281
- _token: csrf_token,
282
- managed: true,
283
- except_view_any: true,
284
- }
285
- let order = {
286
- orderBy: this.orderBy,
287
- orderMode: this.sort[this.orderBy]
288
- }
289
- let page = {
290
- page: this.page
291
- }
292
- let filters = {};
293
- if(this.internalSort) {
294
- filters = {
295
- ...params,
296
- ...formFilters,
297
- ...this.externalFilters,
298
- ...order,
299
- ...page
300
- }
301
- } else {
302
- filters = {
303
- ...params,
304
- ...formFilters,
305
- ...order,
306
- ...this.externalFilters,
307
- ...page
308
- }
309
- }
310
- return filters;
311
- },
312
- updateFilters() {
313
- this.model.setFilters(this.getFilters());
314
- this.fetchData();
315
- },
316
- sortColumn(data) {
317
- if(data.sortable == true) {
318
- // Esto hace que se haga caso omiso a la ordenación de externalFilters
319
- this.internalSort = true;
320
- let column_id = data.id;
321
- // Determinar la columan que va a ordenar los resultados
322
- this.orderBy = column_id;
323
- // Encontrar el orden actual
324
- let order = (this.sort[column_id] == 'asc') ? 'desc' : 'asc';
325
- // Determinar el nuevo orden para la columna dada
326
- this.sort[column_id] = order;
327
- // Solicitar los datos
328
- this.updateFilters();
329
- }
330
- },
331
- updatePage(page) {
332
- this.page = page;
333
- this.updateFilters();
334
- },
335
- actionClicked(action) {
336
- this.model[action.callback](action.params).then( res => {
337
- this.updateFilters();
338
- }).catch( error => {
339
- console.error(error);
340
- });
341
- },
342
- actionButtonClicked(actions) {
343
- const id = actions[0].params.id;
344
- const requestData = {
345
- _token: csrf_token,
346
- id: id
347
- };
348
- const requestConfig = {
349
- method: this.policyMethod,
350
- url: this.policyUrl,
351
- data: this.policyMethod === 'post' ? requestData : null,
352
- params: this.policyMethod === 'get' ? requestData : null
353
- };
354
- axios(requestConfig).then(res => {
355
- this.fetchPoliciesAttempts = 0;
356
- const policies = res.data;
357
- actions.forEach(action => {
358
- if (policies[action.id]) {
359
- action.policy = true;
360
- }
361
- });
362
- }).catch(error => {
363
- if (this.fetchPoliciesAttempts <= 3) {
364
- setTimeout(() => {
365
- ++this.fetchPoliciesAttempts;
366
- this.actionButtonClicked(actions);
367
- }, 1500);
368
- } else {
369
- setTimeout(() => {
370
- this.fetchPoliciesAttempts = 0;
371
- }, 3000);
372
- }
373
- });
374
- },
375
- closeDropdown(event){
376
- let dropdown = event.target.closest('.uk-dropdown');
377
- UIkit.dropdown(dropdown).hide(false);
378
- }
379
- }
380
- }
381
- </script>
382
-
383
- <style scoped>
384
- .tools-icon {
385
- font-size: 30px;
386
- color: var(--secondary-color);
387
- padding: 10px 15px;
388
- }
389
- </style>
1
+ <template>
2
+ <div>
3
+ <div v-if="showTopbar">
4
+ <div class="fe-container fe-container-wide pt-4">
5
+ <div fe-grid>
6
+ <div class="fe-w-expand" v-if="hasActions">
7
+ <button
8
+ class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 mr-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
9
+ @click="actionButtonClicked(crudActions)">
10
+ Acciones
11
+ </button>
12
+ <NavDropdownComponent id="actionCrudDropdown" pos="right">
13
+ <li v-for="action in crudActions" :key="action.id">
14
+ <template v-if="action.route">
15
+ <IconRouteComponent
16
+ v-if="action.policy"
17
+ :name="action.params.to.name"
18
+ :params="{...action.params.to.params, ...extraParams}"
19
+ :query="action.params.to.query ? {...action.params.to.query, ...extraQuery} : {...extraQuery}"
20
+ :icon="action.icon"
21
+ :text="action.name" />
22
+ <DisabledLinkComponent
23
+ v-else
24
+ :icon="action.icon"
25
+ :text="action.name"/>
26
+ </template>
27
+ <template v-else>
28
+ <IconLinkComponent
29
+ v-if="action.policy"
30
+ :icon="action.icon"
31
+ :text="action.name"
32
+ @click="actionClicked(action) , closeDropdown($event)" />
33
+ <DisabledLinkComponent
34
+ v-else
35
+ :icon="action.icon"
36
+ :text="action.name"/>
37
+ </template>
38
+ </li>
39
+ </NavDropdownComponent>
40
+ </div>
41
+ <div v-else>
42
+ <div class="fe-w-expand"></div>
43
+ </div>
44
+ <div v-if="hasFilter" class="fe-w-auto">
45
+ <div class="fe-grid-divider fe-children-expand fe-text-center" fe-grid>
46
+ <div>
47
+ <span
48
+ class="fe-text-right pointer"
49
+ :uk-tooltip="`title: ${'Update results'}`"
50
+ @click="updateFilters">
51
+ <svg class="w-6 h-6 text-slate-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 20">
52
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 1v5h-5M2 19v-5h5m10-4a8 8 0 0 1-14.947 3.97M1 10a8 8 0 0 1 14.947-3.97"/>
53
+ </svg>
54
+ </span>
55
+ </div>
56
+ <div>
57
+ <span
58
+ class="fe-text-right pointer"
59
+ uk-toggle="target: .filter-form; animation: uk-animation-scale-up;"
60
+ uk-tooltip="title: Buscar">
61
+ <svg class="w-6 h-6 text-slate-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 18">
62
+ <path d="M18.85 1.1A1.99 1.99 0 0 0 17.063 0H2.937a2 2 0 0 0-1.566 3.242L6.99 9.868 7 14a1 1 0 0 0 .4.8l4 3A1 1 0 0 0 13 17l.01-7.134 5.66-6.676a1.99 1.99 0 0 0 .18-2.09Z"/>
63
+ </svg>
64
+ </span>
65
+ </div>
66
+ </div>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ <div
71
+ v-if="hasFilter"
72
+ class="filter-form fe-card fe-card-body fe-pt-0"
73
+ hidden>
74
+ <slot name="filterForm"></slot>
75
+ </div>
76
+ </div>
77
+ <div
78
+ class="fe-container fe-container-wide"
79
+ :class="{ 'ptb-20': showTopbar }">
80
+ <div
81
+ class="fe-p-sm"
82
+ :class="{ 'bg-white p-6 rounded-lg shadow dark:border-slate-700 dark:bg-slate-800': cardWrapper }">
83
+ <DataTableComponent
84
+ :actions="hasActions"
85
+ :data-table="dataTable"
86
+ :extra-params="extraParams"
87
+ :extra-query="extraQuery"
88
+ :show-table-header="showTableHeader"
89
+ :data-table-components="dataTableComponents"
90
+ @sortColumn="sortColumn"
91
+ @actionButtonClicked="actionButtonClicked"
92
+ @actionClicked="actionClicked" />
93
+ <SelectPaginationComponent
94
+ :meta="pagination.meta"
95
+ :links="pagination.links"
96
+ @updatePage="updatePage" />
97
+ </div>
98
+ </div>
99
+ </div>
100
+ </template>
101
+
102
+ <script setup>
103
+
104
+ import { markRaw, onMounted, reactive, ref, watch } from 'vue'
105
+ import axios from 'axios'
106
+
107
+ import NavDropdownComponent from './components/NavDropdownComponent.vue'
108
+ import IconRouteComponent from './components/IconRouteComponent.vue'
109
+ import IconLinkComponent from './components/IconLinkComponent.vue'
110
+ import DisabledLinkComponent from './components/DisabledLinkComponent.vue'
111
+ import DataTableComponent from './components/DataTableComponent.vue'
112
+ import SelectPaginationComponent from './components/SelectPaginationComponent.vue'
113
+
114
+ const props = defineProps({
115
+ dataUrl: {
116
+ type: String,
117
+ required: true
118
+ },
119
+ dataMethod: {
120
+ type: String,
121
+ default: 'post',
122
+ },
123
+ model: {
124
+ type: Object,
125
+ required: true
126
+ },
127
+ policyUrl: {
128
+ type: String,
129
+ required: true
130
+ },
131
+ policyMethod: {
132
+ type: String,
133
+ default: 'post'
134
+ },
135
+ showTopbar:{
136
+ type: Boolean,
137
+ default: true
138
+ },
139
+ hasActions: {
140
+ type: Boolean,
141
+ default: true,
142
+ },
143
+ hasFilter: {
144
+ type: Boolean,
145
+ default: true,
146
+ },
147
+ // Vue 3 exige factoria en los defaults de objeto y array.
148
+ formFilters: {
149
+ type: Object,
150
+ default: () => ({})
151
+ },
152
+ externalFilters: {
153
+ type: Object,
154
+ default: () => ({})
155
+ },
156
+ extraParams: {
157
+ type: Object,
158
+ default: () => ({})
159
+ },
160
+ extraQuery: {
161
+ type: Object,
162
+ default: () => ({})
163
+ },
164
+ hideColumns: {
165
+ type: Array,
166
+ default: () => []
167
+ },
168
+ cardWrapper: {
169
+ type: Boolean,
170
+ default: true
171
+ },
172
+ showTableHeader: {
173
+ type: Boolean,
174
+ default: true,
175
+ }
176
+ })
177
+
178
+ /**
179
+ * Se leia de la global `csrf_token`, que la aplicacion anfitriona tenia
180
+ * que definir en window: el componente no se podia montar fuera de ella.
181
+ */
182
+ const csrfToken = () => globalThis.csrf_token
183
+ ?? document.querySelector('meta[name="csrf-token"]')?.getAttribute('content')
184
+ ?? ''
185
+
186
+ /**
187
+ * Sustituye a `_.isEqual` de lodash, que se usaba como global sin
188
+ * declararla como dependencia.
189
+ */
190
+ const isEqual = (a, b) => {
191
+
192
+ if (a === b) {
193
+ return true
194
+ }
195
+
196
+ if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) {
197
+ return false
198
+ }
199
+
200
+ const keysA = Object.keys(a)
201
+ const keysB = Object.keys(b)
202
+
203
+ return keysA.length === keysB.length && keysA.every((key) => isEqual(a[key], b[key]))
204
+
205
+ }
206
+
207
+ const hiddenColumnIds = () => props.hideColumns.map(
208
+ // Se admite tanto ['name'] como [{ id: 'name' }]: el contrato nunca
209
+ // estuvo documentado y por ahi circulan las dos formas.
210
+ (column) => typeof column === 'string' ? column : column?.id
211
+ )
212
+
213
+ const buildHead = () => {
214
+
215
+ const hidden = hiddenColumnIds()
216
+
217
+ return props.model.dataTableHead().filter((column) => ! hidden.includes(column.id))
218
+
219
+ }
220
+
221
+ const registerComponents = () => {
222
+
223
+ if (typeof props.model.dataTableComponents !== 'function') {
224
+ return {}
225
+ }
226
+
227
+ const declared = props.model.dataTableComponents()
228
+
229
+ return Object.keys(declared).reduce((components, key) => {
230
+ components[key] = markRaw(declared[key])
231
+
232
+ return components
233
+ }, {})
234
+
235
+ }
236
+
237
+ const crudActions = ref(props.model.crudActions())
238
+
239
+ const dataTable = reactive({
240
+ head: buildHead(),
241
+ body: [],
242
+ })
243
+
244
+ const pagination = reactive({
245
+ meta: [],
246
+ links: [],
247
+ })
248
+
249
+ const dataTableComponents = registerComponents()
250
+
251
+ const sort = reactive(props.model.dataTableSort())
252
+
253
+ const orderBy = ref('id')
254
+ const internalSort = ref(false)
255
+ const page = ref(1)
256
+
257
+ let fetchDataAttempts = 0
258
+ let fetchPoliciesAttempts = 0
259
+
260
+ const getFilters = () => {
261
+
262
+ const params = {
263
+ _token: csrfToken(),
264
+ managed: true,
265
+ except_view_any: true,
266
+ }
267
+
268
+ const order = {
269
+ orderBy: orderBy.value,
270
+ orderMode: sort[orderBy.value],
271
+ }
272
+
273
+ // Con orden interno, el del usuario gana a lo que traigan los filtros
274
+ // externos; sin el, es al reves.
275
+ return internalSort.value
276
+ ? { ...params, ...props.formFilters, ...props.externalFilters, ...order, page: page.value }
277
+ : { ...params, ...props.formFilters, ...order, ...props.externalFilters, page: page.value }
278
+
279
+ }
280
+
281
+ const fetchData = () => {
282
+
283
+ const filters = getFilters()
284
+
285
+ return axios({
286
+ method: props.dataMethod,
287
+ url: props.dataUrl,
288
+ data: props.dataMethod === 'post' ? filters : null,
289
+ params: props.dataMethod === 'get' ? filters : null,
290
+ }).then((res) => {
291
+
292
+ fetchDataAttempts = 0
293
+ dataTable.body = res.data.data
294
+ pagination.meta = res.data.meta
295
+ pagination.links = res.data.links
296
+
297
+ }).catch((error) => {
298
+
299
+ // Un fallo de red no trae respuesta: leer error.response.status
300
+ // sin comprobarlo lanzaba un TypeError dentro del propio manejador.
301
+ if (error.response?.status === 403) {
302
+ return
303
+ }
304
+
305
+ if (fetchDataAttempts <= 3) {
306
+ setTimeout(() => {
307
+ ++fetchDataAttempts
308
+ fetchData()
309
+ }, 1500)
310
+ }
311
+
312
+ })
313
+
314
+ }
315
+
316
+ const updateFilters = () => {
317
+ props.model.setFilters(getFilters())
318
+
319
+ return fetchData()
320
+ }
321
+
322
+ const sortColumn = (column) => {
323
+
324
+ if (column.sortable !== true) {
325
+ return
326
+ }
327
+
328
+ // A partir de aqui el orden elegido por el usuario manda sobre el que
329
+ // puedan traer los filtros externos.
330
+ internalSort.value = true
331
+ orderBy.value = column.id
332
+ sort[column.id] = sort[column.id] === 'asc' ? 'desc' : 'asc'
333
+
334
+ updateFilters()
335
+
336
+ }
337
+
338
+ const updatePage = (newPage) => {
339
+ page.value = newPage
340
+
341
+ updateFilters()
342
+ }
343
+
344
+ const actionClicked = (action) => {
345
+ return props.model[action.callback](action.params)
346
+ .then(() => updateFilters())
347
+ .catch((error) => console.error(error))
348
+ }
349
+
350
+ const actionButtonClicked = (actions) => {
351
+
352
+ const requestData = {
353
+ _token: csrfToken(),
354
+ id: actions[0]?.params?.id ?? null,
355
+ }
356
+
357
+ return axios({
358
+ method: props.policyMethod,
359
+ url: props.policyUrl,
360
+ data: props.policyMethod === 'post' ? requestData : null,
361
+ params: props.policyMethod === 'get' ? requestData : null,
362
+ }).then((res) => {
363
+
364
+ fetchPoliciesAttempts = 0
365
+
366
+ actions.forEach((action) => {
367
+ if (res.data[action.id]) {
368
+ action.policy = true
369
+ }
370
+ })
371
+
372
+ }).catch(() => {
373
+
374
+ if (fetchPoliciesAttempts <= 3) {
375
+ setTimeout(() => {
376
+ ++fetchPoliciesAttempts
377
+ actionButtonClicked(actions)
378
+ }, 1500)
379
+
380
+ return
381
+ }
382
+
383
+ setTimeout(() => {
384
+ fetchPoliciesAttempts = 0
385
+ }, 3000)
386
+
387
+ })
388
+
389
+ }
390
+
391
+ const closeDropdown = (event) => {
392
+
393
+ const dropdown = event.target.closest('.uk-dropdown')
394
+
395
+ if (dropdown) {
396
+ globalThis.UIkit?.dropdown(dropdown)?.hide(false)
397
+ }
398
+
399
+ }
400
+
401
+ watch(() => props.formFilters, () => {
402
+ page.value = 1
403
+ updateFilters()
404
+ }, { deep: true })
405
+
406
+ watch(() => props.externalFilters, (value, previous) => {
407
+ if (! isEqual(value, previous)) {
408
+ updateFilters()
409
+ }
410
+ }, { deep: true })
411
+
412
+ onMounted(fetchData)
413
+
414
+ // El test y el anfitrion consultan el estado de las acciones; con
415
+ // <script setup> los bindings son privados si no se exponen.
416
+ defineExpose({ crudActions, dataTable, pagination })
417
+
418
+ </script>
419
+
420
+ <style scoped>
421
+ .tools-icon {
422
+ font-size: 30px;
423
+ color: var(--secondary-color);
424
+ padding: 10px 15px;
425
+ }
426
+ </style>