v-sistec-features 1.25.0 → 1.26.2

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "v-sistec-features",
3
3
  "private": false,
4
- "version": "1.25.0",
4
+ "version": "1.26.2",
5
5
  "author": "Márlon Bento Azevedo (https://github.com/marlon-bento)",
6
6
  "repository": {
7
7
  "type": "git",
@@ -1,58 +1,60 @@
1
1
  <template>
2
- <div class="dropdown d-flex">
3
- <a v-if="item_use.includes(2)" href="#" class="btn dropdown-toggle" data-bs-toggle="dropdown"
4
- title="Pesquisas Prédefinidas">
5
- <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-filter" width="24" height="24"
6
- viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
7
- stroke-linejoin="round">
2
+
3
+
4
+ <a v-if="item_use.includes(2)" href="#" class="btn dropdown-toggle" data-bs-toggle="dropdown"
5
+ title="Pesquisas Prédefinidas">
6
+ <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-filter" width="24" height="24"
7
+ viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
8
+ stroke-linejoin="round">
9
+ <path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
10
+ <path
11
+ d="M4 4h16v2.172a2 2 0 0 1 -.586 1.414l-4.414 4.414v7l-6 2v-8.5l-4.48 -4.928a2 2 0 0 1 -.52 -1.345v-2.227z">
12
+ </path>
13
+ </svg>
14
+ </a>
15
+ <div class="input-icon">
16
+ <input type="text" class="form-control ms-1" id="inputSearchVDataTable" v-model="modelSearch"
17
+ @keyup.enter="searchEnter" :placeholder="placeholder_search">
18
+
19
+ <span v-if="modelSearch" @click="cleanSearch()" class=" inputClose" title="Limpar pesquisa">
20
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
21
+ stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
22
+ class="icon icon-tabler icons-tabler-outline icon-tabler-x">
23
+ <path stroke="none" d="M0 0h24v24H0z" fill="none" />
24
+ <path d="M18 6l-12 12" />
25
+ <path d="M6 6l12 12" />
26
+ </svg>
27
+ </span>
28
+ <span v-else class="input-icon-addon">
29
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
30
+ stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon">
8
31
  <path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
9
- <path
10
- d="M4 4h16v2.172a2 2 0 0 1 -.586 1.414l-4.414 4.414v7l-6 2v-8.5l-4.48 -4.928a2 2 0 0 1 -.52 -1.345v-2.227z">
11
- </path>
32
+ <path d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0"></path>
33
+ <path d="M21 21l-6 -6"></path>
12
34
  </svg>
13
- </a>
14
- <div class="input-icon">
15
- <input type="text" class="form-control ms-1" id="inputSearchVDataTable" v-model="modelSearch"
16
- @keyup.enter="searchEnter" :placeholder="placeholder_search">
17
-
18
- <span v-if="modelSearch" @click="cleanSearch()" class=" inputClose" title="Limpar pesquisa">
19
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
20
- stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
21
- class="icon icon-tabler icons-tabler-outline icon-tabler-x">
22
- <path stroke="none" d="M0 0h24v24H0z" fill="none" />
23
- <path d="M18 6l-12 12" />
24
- <path d="M6 6l12 12" />
25
- </svg>
26
- </span>
27
- <span v-else class="input-icon-addon">
28
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
29
- stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon">
30
- <path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
31
- <path d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0"></path>
32
- <path d="M21 21l-6 -6"></path>
33
- </svg>
34
- </span>
35
- </div>
36
- <div v-if="item_use.includes(2)" class="dropdown-menu">
37
- <template v-for="(filter, index) in props.list_filter" :key="index">
38
- <router-link v-if="('type' in filter ? filter.type === 1 : false) && ('visible' in filter ? filter.visible : true)" :to="filter?.to"
39
- class="dropdown-item cursor-pointer">
40
- {{ filter.text }}
41
- </router-link>
42
- <a v-else-if="('type' in filter ? filter.type === 2 : true) && ('visible' in filter ? filter.visible : true)"
43
- @click.prevent="tradeFilter(String(filter.value))" class="dropdown-item cursor-pointer"
44
- :class="modelFilter === filter?.value ? 'bg-info text-dark selected' : ''">
45
- {{ filter.text }}
46
- </a>
47
- <a v-else-if="('type' in filter ? filter.type === 3 : true) && ('visible' in filter ? filter.visible : true)" @click.prevent="clickItem(filter)"
48
- class="dropdown-item cursor-pointer"
49
- :class="filter?.active ? 'bg-info text-dark selected' : ''"
50
- >
51
- {{ filter.text }}
52
- </a>
53
- </template>
54
- </div>
35
+ </span>
55
36
  </div>
37
+ <div v-if="item_use.includes(2)" class="dropdown-menu">
38
+ <template v-for="(filter, index) in props.list_filter" :key="index">
39
+ <router-link
40
+ v-if="('type' in filter ? filter.type === 1 : false) && ('visible' in filter ? filter.visible : true)"
41
+ :to="filter?.to" class="dropdown-item cursor-pointer">
42
+ {{ filter.text }}
43
+ </router-link>
44
+ <a v-else-if="('type' in filter ? filter.type === 2 : true) && ('visible' in filter ? filter.visible : true)"
45
+ @click.prevent="tradeFilter(String(filter.value))" class="dropdown-item cursor-pointer"
46
+ :class="modelFilter === filter?.value ? 'bg-info text-dark selected' : ''">
47
+ {{ filter.text }}
48
+ </a>
49
+ <a v-else-if="('type' in filter ? filter.type === 3 : true) && ('visible' in filter ? filter.visible : true)"
50
+ @click.prevent="clickItem(filter)" class="dropdown-item cursor-pointer"
51
+ :class="filter?.active ? 'bg-info text-dark selected' : ''">
52
+ {{ filter.text }}
53
+ </a>
54
+ </template>
55
+ </div>
56
+
57
+
56
58
  </template>
57
59
  <script setup lang="ts">
58
60
  import { computed, watch } from 'vue';
@@ -64,6 +66,7 @@ interface SearchProps {
64
66
  placeholder_search?: string;
65
67
  deactivate_search_on_clear?: boolean;
66
68
  deactivate_search_empty?: boolean;
69
+
67
70
  }
68
71
  const props = withDefaults(defineProps<SearchProps>(), {
69
72
  filter: "",
@@ -148,7 +151,7 @@ function tradeFilter(newFilter: string): void {
148
151
 
149
152
  function cleanSearch(): void {
150
153
  modelSearch.value = ""
151
- if (!props.deactivate_search_on_clear){
154
+ if (!props.deactivate_search_on_clear) {
152
155
  emit('search'); // emite o evento de busca para atualizar a lista
153
156
  }
154
157
  emit('clickedClearSearch');
@@ -4,12 +4,13 @@
4
4
 
5
5
  <script setup lang="ts">
6
6
  import { inject, onMounted, useSlots } from 'vue';
7
+ import type { Component } from 'vue';
7
8
  import { dataTableApiKey } from '../keys';
8
9
  const column_internal_id = crypto.randomUUID();
9
10
  defineSlots<{
10
11
  // props para o slot body
11
12
  body?: (props: { item: any }) => any,
12
- col_header?: (props: { col: any, locked: boolean }) => any,
13
+ col_header?: (props: { col: any, locked: boolean, OrderingComponent: Component }) => any,
13
14
  col_header_midle?: (props: { col: any, locked: boolean }) => any,
14
15
  }>();
15
16
  interface VColumnProps {
@@ -19,23 +19,41 @@
19
19
  <slot name="fieldMiddle">
20
20
 
21
21
  </slot>
22
+
22
23
  <Teleport :to="options.search_teleport || 'body'" :disabled="!options.search_teleport">
23
- <slot name="search-field" :search="pagination.search" :filter="pagination.filter" :reSearch="reSearch"
24
- :list_filter="options.list_filter" :item_use="item_use">
24
+ <div class="dropdown d-flex">
25
25
  <Search v-if="!options.disable_search" v-model:search="pagination.search"
26
26
  v-model:filter="pagination.filter" :list_filter="options.list_filter" :item_use="item_use"
27
27
  @search="reSearch" :deactivate_search_on_clear="options.deactivate_search_on_clear"
28
28
  :placeholder_search="options.placeholder_search"
29
29
  :deactivate_search_empty="options.deactivate_search_empty"
30
- @clicked-clear-search="$emit('clickedClearSearch')" />
31
- </slot>
30
+ :use_column_manager="options.use_column_manager" :toggleColumnVisibility="toggleColumnVisibility"
31
+ :columns_list="columns_list" @clicked-clear-search="$emit('clickedClearSearch')">
32
+
33
+ <template #search-field>
34
+ <slot name="search-field" :search="pagination.search" :filter="pagination.filter"
35
+ :reSearch="reSearch">
36
+ </slot>
37
+ </template>
38
+ </Search>
39
+
40
+ <VExtraActions :use_column_manager="options.use_column_manager" :columns_list="columns_list"
41
+ :toggleColumnVisibility="toggleColumnVisibility">
42
+ <template #extra-actions>
43
+ <slot name="extra-actions">
44
+ </slot>
45
+ </template>
46
+ </VExtraActions>
47
+ </div>
48
+
32
49
  </Teleport>
33
50
  </div>
34
51
  <slot name="item-selected-info" :selected_items="selected_items" :clearSelection="() => selected_items = []">
35
52
  <div v-if="(options.use_checkbox && selected_items.length > 0) && !options.deactivate_selected_info"
36
53
  class="alert alert-cyan d-flex justify-content-center align-items-center py-2" role="alert">
37
54
  <h4 class="alert-title m-0"> <strong>oi Itens Selecionados:</strong> <span
38
- class="badge bg-azure text-azure-fg">{{ selected_items.length }}</span></h4>
55
+ class="badge bg-azure text-azure-fg">{{
56
+ selected_items.length }}</span></h4>
39
57
  <a class=" cursor-pointer " @click="selected_items = []">
40
58
  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
41
59
  stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
@@ -72,9 +90,8 @@
72
90
  <table class="table table-vcenter table-selectable" :class="options.class_table">
73
91
  <thead>
74
92
 
75
- <draggable v-model="draggableColumns" tag="tr" item-key="id" :animation="400"
76
- ghost-class="ghost-item" drag-class="dragging-item" @start="isDraggingColumns = true"
77
- @end="() => onDragEnd()">
93
+ <draggable v-model="draggableColumns" tag="tr" item-key="id" :animation="400" ghost-class="ghost-item"
94
+ drag-class="dragging-item" @start="isDraggingColumns = true" @end="() => onDragEnd()">
78
95
 
79
96
  <template #header>
80
97
  <th v-if="options.use_expandable_items"></th>
@@ -247,6 +264,7 @@ import { readonly, ref, provide, computed, watch, nextTick, inject, onMounted }
247
264
  import { DATA_TABLE_CONFIG } from '@/config/datatableConfig'
248
265
  import PaginationDatatable from '@/Pagination/Pagination.vue';
249
266
  import Search from './SearchDatatable.vue';
267
+ import VExtraActions from './VExtraActions.vue';
250
268
  import { useImagePreview } from '../composables/useImagePreview';
251
269
  import { dataTableApiKey, type ColumnConfiguration } from '../keys';
252
270
  import draggable from 'vuedraggable';
@@ -256,6 +274,7 @@ import { useCheckBox } from '../composables/useCheckBox.ts';
256
274
  import VDataTableLoading from './VDataTableLoading.vue';
257
275
  import VThDataTable from './VThDataTable.vue';
258
276
 
277
+
259
278
  const globalConfig = inject(DATA_TABLE_CONFIG, {});
260
279
  const {
261
280
  isHovering,
@@ -295,6 +314,8 @@ const props = withDefaults(defineProps<VDataTableProps>(), {
295
314
  pagination_teleport: null,
296
315
  search_teleport: null,
297
316
  show_header_when_empty: false,
317
+ storage_id: '',
318
+ use_column_manager: false,
298
319
  });
299
320
  const options = computed(() => {
300
321
  return {
@@ -334,6 +355,8 @@ const options = computed(() => {
334
355
  retry_attempts: props.retry_attempts ?? globalConfig.retry_attempts ?? 3,
335
356
  retry_delay: props.retry_delay ?? globalConfig.retry_delay ?? 2000,
336
357
  show_header_when_empty: props.show_header_when_empty ?? globalConfig.show_header_when_empty ?? false,
358
+ storage_id: props.storage_id ?? globalConfig.storage_id ?? '',
359
+ use_column_manager: props.use_column_manager ?? globalConfig.use_column_manager ?? false,
337
360
  };
338
361
  });
339
362
  const emit = defineEmits(['tradePage', 'beforeFetch', 'afterFetch', 'clickedClearSearch']);
@@ -388,6 +411,92 @@ const {
388
411
  atLeastOneSelected, toggleSelectAll, toggleItemSelection
389
412
  } = useCheckBox(options.value, items);
390
413
 
414
+
415
+
416
+ // Variável computada que forma uma string exclusiva para garantir que este cache não atropele o cache de outra tabela.
417
+ const storageKey = computed(() => {
418
+ if (!options.value.storage_id) return null;
419
+
420
+ return `vdatatable_${options.value.storage_id}_${options.value.endpoint}`;
421
+ });
422
+
423
+ /*
424
+ Recebe: Nada.
425
+ Devolve: Nada.
426
+ Por que é usada: Inspeciona cada coluna para gerar um ID confiável de cache. Se for incapaz de gerar IDs únicos sem conflitos, a aplicação é encerrada propositalmente.
427
+ */
428
+ function validateAndAssignKeys(): void {
429
+ for (const col of columns.value) {
430
+ let key = '';
431
+ const fieldCount = columns.value.filter(c => c.field === col.field).length;
432
+
433
+ if (col.field && fieldCount === 1) {
434
+ key = col.field;
435
+ } else {
436
+ const headerCount = columns.value.filter(c => c.header === col.header).length;
437
+ if (col.header && headerCount === 1) {
438
+ key = col.header;
439
+ } else {
440
+ throw new Error(`[VDataTable] Falha de integridade: Impossível designar um ID único de armazenamento. O 'field' e 'header' estão duplicados ou ausentes. Header ofensor: ${col.header}`);
441
+ }
442
+ }
443
+ col.storage_key = key;
444
+ }
445
+ }
446
+
447
+ /*
448
+ Recebe: Nada.
449
+ Devolve: Nada.
450
+ Por que é usada: Converte o estado de visibilidade e a ordem das colunas para JSON e grava no navegador do usuário.
451
+ */
452
+ function saveColumnsState(): void {
453
+ if (!storageKey.value) return;
454
+ const state = columns.value.map(col => ({
455
+ storage_key: col.storage_key,
456
+ visible: col.visible
457
+ }));
458
+ localStorage.setItem(storageKey.value, JSON.stringify(state));
459
+ }
460
+
461
+ /*
462
+ Recebe: Nada.
463
+ Devolve: Nada.
464
+ Por que é usada: Tenta recuperar o estado anterior da tabela. Caso detecte mudanças na estrutura das colunas, ele expurga o cache velho e impõe a nova realidade.
465
+ */
466
+ function loadColumnsState(): void {
467
+ if (!storageKey.value) return;
468
+ const saved = localStorage.getItem(storageKey.value);
469
+ if (!saved) return;
470
+
471
+ try {
472
+ const parsed = JSON.parse(saved);
473
+ const currentKeys = columns.value.map(c => c.storage_key).sort();
474
+ const savedKeys = parsed.map((p: any) => p.storage_key).sort();
475
+
476
+ if (JSON.stringify(currentKeys) !== JSON.stringify(savedKeys)) {
477
+ localStorage.removeItem(storageKey.value);
478
+ saveColumnsState();
479
+ return;
480
+ }
481
+
482
+ const newColumnsOrder = [];
483
+ for (const savedCol of parsed) {
484
+ const found = columns.value.find(c => c.storage_key === savedCol.storage_key);
485
+ if (found) {
486
+ found.visible = savedCol.visible;
487
+ newColumnsOrder.push(found);
488
+ }
489
+ }
490
+
491
+ // Atualiza o array in-place para não destruir a instância que o vuedraggable vigia
492
+ columns.value.splice(0, columns.value.length, ...newColumnsOrder);
493
+
494
+ } catch (e) {
495
+ localStorage.removeItem(storageKey.value);
496
+ }
497
+ }
498
+
499
+
391
500
  // =======================================================
392
501
  // 4. PROPRIEDADES COMPUTADAS
393
502
  // =======================================================
@@ -404,7 +513,9 @@ const draggableColumns = computed({
404
513
  set(newUnlockedOrder) {
405
514
  const locked = columns.value.filter(c => c.locked);
406
515
  const hidden = columns.value.filter(c => !c.locked && c.visible === false);
407
- columns.value = [...newUnlockedOrder, ...hidden, ...locked];
516
+ // Usa splice para não quebrar a referência de reatividade do array original!
517
+ columns.value.splice(0, columns.value.length, ...newUnlockedOrder, ...hidden, ...locked);
518
+ saveColumnsState();
408
519
  }
409
520
  });
410
521
  // colunas RENDERIZADAS (ordem final)
@@ -435,6 +546,7 @@ function toggleColumnVisibility(id: string): void {
435
546
  const coluna = columns.value.find(c => c.id === id);
436
547
  if (coluna) {
437
548
  coluna.visible = !coluna.visible;
549
+ saveColumnsState();
438
550
  }
439
551
  }
440
552
 
@@ -470,7 +582,6 @@ watch(response, (newResponse: any) => {
470
582
  }, { immediate: true });
471
583
 
472
584
 
473
-
474
585
  // =======================================================
475
586
  // 6. MÉTODOS
476
587
  // =======================================================
@@ -635,14 +746,40 @@ onMounted(() => {
635
746
  reSearch();
636
747
  });
637
748
  }
749
+ nextTick(() => {
750
+ if (options.value.storage_id) {
751
+ validateAndAssignKeys();
752
+ loadColumnsState();
753
+ }
754
+ });
638
755
  });
639
756
 
640
757
  watch(
641
- () => options.value.add_params,
642
758
  () => {
643
- reSearch();
759
+ const params = typeof options.value.add_params === 'function'
760
+ ? options.value.add_params()
761
+ : options.value.add_params;
762
+ return JSON.stringify(params);
644
763
  },
645
- { deep: true }
764
+ (newVal, oldVal) => {
765
+ if (newVal !== oldVal && oldVal !== undefined) {
766
+ reSearch();
767
+ }
768
+ }
769
+ );
770
+ /*
771
+ ========
772
+ Observa mudanças no endpoint.
773
+ Caso a rota da API mude dinamicamente, ele reseta a tabela para a página inicial com segurança.
774
+ ========
775
+ */
776
+ watch(
777
+ () => options.value.endpoint,
778
+ (newVal, oldVal) => {
779
+ if (newVal !== oldVal && oldVal !== undefined) {
780
+ pagination.value.current_page = options.value.page_starts_at;
781
+ }
782
+ }
646
783
  );
647
784
  </script>
648
785
 
@@ -0,0 +1,45 @@
1
+ <template>
2
+
3
+ <div v-if="use_column_manager" class="dropdown d-inline-block ms-2">
4
+ <button class="btn dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false"
5
+ title="Gerenciar Colunas visíveis">
6
+ <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-columns" width="24" height="24"
7
+ viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
8
+ stroke-linejoin="round">
9
+ <path stroke="none" d="M0 0h24v24H0z" fill="none" />
10
+ <path d="M4 6l5.5 0" />
11
+ <path d="M4 10l5.5 0" />
12
+ <path d="M4 14l5.5 0" />
13
+ <path d="M4 18l5.5 0" />
14
+ <path d="M14.5 6l5.5 0" />
15
+ <path d="M14.5 10l5.5 0" />
16
+ <path d="M14.5 14l5.5 0" />
17
+ <path d="M14.5 18l5.5 0" />
18
+ </svg>
19
+ </button>
20
+ <div class="dropdown-menu dropdown-menu-end p-3 shadow-sm" style="min-width: 220px; z-index: 1050;">
21
+ <h6 class="dropdown-header text-uppercase fw-bold mb-2">Exibir Colunas</h6>
22
+ <div class="d-flex flex-column gap-2">
23
+ <label v-for="coluna in columns_list" :key="coluna.id" class="form-check cursor-pointer m-0">
24
+ <input class="form-check-input" type="checkbox" :checked="coluna.visible" :disabled="coluna.locked"
25
+ @change="toggleColumnVisibility(coluna.id)">
26
+ <span class="form-check-label user-select-none">{{ coluna.header }}</span>
27
+ </label>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </template>
32
+ <script setup lang="ts">
33
+ interface SearchProps {
34
+ columns_list?: any[];
35
+ use_column_manager?: boolean;
36
+ toggleColumnVisibility?: (columnId: string) => void;
37
+ }
38
+ withDefaults(defineProps<SearchProps>(), {
39
+ columns_list: () => [],
40
+ use_column_manager: false,
41
+ toggleColumnVisibility: () => { }
42
+ });
43
+
44
+
45
+ </script>
@@ -0,0 +1,50 @@
1
+ <template>
2
+ <span v-if="use_ordering" @click="emit('toggle')" class="cursor-pointer">
3
+ <svg v-if="!props.orderings_state || props.orderings_state === 'none'" xmlns="http://www.w3.org/2000/svg"
4
+ width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
5
+ stroke-linecap="round" stroke-linejoin="round">
6
+ <path d="m3 8 4-4 4 4"></path>
7
+ <path d="m11 16-4 4-4-4"></path>
8
+ <path d="M7 4v16"></path>
9
+ <path d="M15 8h6"></path>
10
+ <path d="M15 16h6"></path>
11
+ <path d="M13 12h8"></path>
12
+ </svg>
13
+
14
+ <svg v-else-if="props.orderings_state === 'decreasing'" xmlns="http://www.w3.org/2000/svg" width="16"
15
+ height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
16
+ stroke-linejoin="round">
17
+ <path d="m3 16 4 4 4-4"></path>
18
+ <path d="M7 20V4"></path>
19
+ <path d="M11 4h10"></path>
20
+ <path d="M11 8h7"></path>
21
+ <path d="M11 12h4"></path>
22
+ </svg>
23
+
24
+ <svg v-else-if="props.orderings_state === 'increasing'" xmlns="http://www.w3.org/2000/svg" width="16"
25
+ height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
26
+ stroke-linejoin="round">
27
+ <path d="m3 8 4-4 4 4"></path>
28
+ <path d="M7 4v16"></path>
29
+ <path d="M11 12h4"></path>
30
+ <path d="M11 16h7"></path>
31
+ <path d="M11 20h10"></path>
32
+ </svg>
33
+ </span>
34
+ </template>
35
+
36
+ <script setup lang="ts">
37
+ /*
38
+ ========
39
+ Componente interno de uso exclusivo do VDataTable para renderizar o ícone de ordenação.
40
+ ========
41
+ */
42
+ const props = defineProps<{
43
+ orderings_state?: 'none' | 'increasing' | 'decreasing';
44
+ use_ordering?: boolean;
45
+ }>();
46
+
47
+ const emit = defineEmits<{
48
+ toggle: [void];
49
+ }>();
50
+ </script>
@@ -1,56 +1,22 @@
1
1
  <template>
2
2
  <th :class="class_all">
3
3
  <div :class="{ 'header-ordering': use_ordering }">
4
-
5
- <component
6
- v-if="hasCustomHeader"
7
- :is="col.colHeaderSlot"
8
- v-bind="{ col: props.col, locked: props.locked }"
9
- ></component>
10
-
11
- <template v-else>
12
- <span>
13
- {{ props.header }}
14
- </span>
15
4
 
16
- <component
17
- v-if="col.colHeaderMidleSlot"
18
- :is="col.colHeaderMidleSlot"
19
- v-bind="{ col: props.col, locked: props.locked }"
20
- ></component>
5
+ <component v-if="hasCustomHeader" :is="col.colHeaderSlot"
6
+ v-bind="{ col: props.col, locked: props.locked, OrderingComponent }"></component>
7
+
8
+ <template v-else>
9
+ <div class="d-flex justify-content-between align-items-center">
10
+ <span>
11
+ {{ props.header }}
12
+ </span>
21
13
 
22
- <span v-if="use_ordering" @click="emit('toggleOrderingState')" class="ms-2 cursor-pointer">
23
- <svg v-if="!props.orderings_state || props.orderings_state === 'none'" xmlns="http://www.w3.org/2000/svg"
24
- width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
25
- stroke-linecap="round" stroke-linejoin="round">
26
- <path d="m3 8 4-4 4 4"></path>
27
- <path d="m11 16-4 4-4-4"></path>
28
- <path d="M7 4v16"></path>
29
- <path d="M15 8h6"></path>
30
- <path d="M15 16h6"></path>
31
- <path d="M13 12h8"></path>
32
- </svg>
14
+ <component v-if="col.colHeaderMidleSlot" :is="col.colHeaderMidleSlot"
15
+ v-bind="{ col: props.col, locked: props.locked }"></component>
33
16
 
34
- <svg v-else-if="props.orderings_state === 'decreasing'" xmlns="http://www.w3.org/2000/svg" width="16"
35
- height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
36
- stroke-linejoin="round">
37
- <path d="m3 16 4 4 4-4"></path>
38
- <path d="M7 20V4"></path>
39
- <path d="M11 4h10"></path>
40
- <path d="M11 8h7"></path>
41
- <path d="M11 12h4"></path>
42
- </svg>
17
+ <component :is="OrderingComponent" />
18
+ </div>
43
19
 
44
- <svg v-else-if="props.orderings_state === 'increasing'" xmlns="http://www.w3.org/2000/svg" width="16"
45
- height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
46
- stroke-linejoin="round">
47
- <path d="m3 8 4-4 4 4"></path>
48
- <path d="M7 4v16"></path>
49
- <path d="M11 12h4"></path>
50
- <path d="M11 16h7"></path>
51
- <path d="M11 20h10"></path>
52
- </svg>
53
- </span>
54
20
  </template>
55
21
 
56
22
  </div>
@@ -58,8 +24,8 @@
58
24
  </template>
59
25
 
60
26
  <script setup lang="ts">
61
- import { computed, Comment, Fragment, Text } from 'vue';
62
-
27
+ import { computed, Comment, Fragment, Text, h } from 'vue';
28
+ import VOrderingIcon from './VOrderingIcon.vue';
63
29
  const props = defineProps<{
64
30
  header: string;
65
31
  class_column: string;
@@ -68,7 +34,11 @@ const props = defineProps<{
68
34
  orderings_state: 'none' | 'increasing' | 'decreasing';
69
35
  col: any;
70
36
  }>();
71
-
37
+ const OrderingComponent = () => h(VOrderingIcon, {
38
+ orderings_state: props.orderings_state,
39
+ use_ordering: props.use_ordering,
40
+ onToggle: () => emit('toggleOrderingState')
41
+ });
72
42
  const class_all = computed(() => {
73
43
  return {
74
44
  'header-draggable': !props.locked,
@@ -15,10 +15,9 @@ export function useDataTableFetch<T>(
15
15
  const items = ref<T[]>([]) as Ref<T[]>;
16
16
 
17
17
  const urlReativa = computed(() => {
18
- pagination.value.current_page = props.page_starts_at;
19
18
  return props.endpoint;
20
19
  });
21
-
20
+
22
21
  const default_params = computed<Record<string, any>>(() => ({
23
22
  [props.page_param_name]: pagination.value.current_page + 1,
24
23
  [props.page_size_param_name]: pagination.value.limit_per_page,
@@ -26,6 +26,8 @@ export interface ColumnConfiguration {
26
26
  // não é prop, é uma propriedade interna do VDataTable para controlar a visibilidade da coluna
27
27
  visible?: boolean;
28
28
  id: string; // ID único da coluna, gerado internamente pelo VDataTable
29
+ // Chave gerada dinamicamente para salvar e recuperar o estado da coluna no localstorage
30
+ storage_key?: string;
29
31
  }
30
32
 
31
33
  // A API que o VDataTable "fornece" para os filhos
@@ -78,6 +78,9 @@ export interface VDataTableProps {
78
78
  pagination_teleport?: string | HTMLElement | null;
79
79
  search_teleport?: string | HTMLElement | null;
80
80
  show_header_when_empty?: boolean;
81
+ // Propriedade para ativar a persistência de configuração da tabela no localstorage
82
+ storage_id?: string;
83
+ use_column_manager?: boolean;
81
84
  }
82
85
  export type DataTablePropsWithDefaults = VDataTableProps & {
83
86
  // Strings
@@ -129,6 +132,8 @@ export type DataTablePropsWithDefaults = VDataTableProps & {
129
132
  add_params: Record<string, any> | (() => Record<string, any>);
130
133
  list_filter: any[];
131
134
  custom_loading: any | null;
135
+ storage_id: string;
136
+ use_column_manager: boolean;
132
137
  };
133
138
  export interface ExposedFunctions<T extends Record<string, any>> {
134
139
  execute: () => void;