v-sistec-features 1.5.0 → 1.6.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "v-sistec-features",
3
3
  "private": false,
4
- "version": "1.5.0",
4
+ "version": "1.6.1",
5
5
  "author": "Márlon Bento Azevedo (https://github.com/marlon-bento)",
6
6
  "repository": {
7
7
  "type": "git",
@@ -46,7 +46,9 @@
46
46
  "peerDependencies": {
47
47
  "@tabler/icons-vue": "^3.35.0",
48
48
  "vue": "^3.2.0",
49
- "vuedraggable": "^4.1.0"
49
+ "vuedraggable": "^4.1.0",
50
+ "v-api-fetch": "^1.3.0",
51
+ "v-required": "^2.0.2"
50
52
  },
51
53
  "devDependencies": {
52
54
  "@semantic-release/changelog": "^6.0.3",
@@ -5,13 +5,14 @@ import { computed } from "vue";
5
5
  // 1. DEFINIÇÃO DE TIPOS E INTERFACES
6
6
  // =======================================================
7
7
  interface PaginationObject {
8
- current_page: number;
8
+ current_page: number; // Este é o valor da API (pode ser 0 ou 1 para a primeira pág)
9
9
  count: number;
10
10
  limit_per_page: number;
11
11
  }
12
12
  interface PaginationProps {
13
13
  pagination: PaginationObject;
14
14
  filtering?: boolean;
15
+ page_starts_at?: number; // 0 ou 1, define o que a API espera para a primeira página
15
16
  }
16
17
 
17
18
  // =======================================================
@@ -19,6 +20,7 @@ interface PaginationProps {
19
20
  // =======================================================
20
21
  const props = withDefaults(defineProps<PaginationProps>(), {
21
22
  filtering: false,
23
+ page_starts_at: 0 // Padrão para API base 0
22
24
  });
23
25
  const emit = defineEmits<{
24
26
  (e: 'tradePage'): void
@@ -33,8 +35,8 @@ const total_pages = computed<number>(() => {
33
35
  });
34
36
 
35
37
  const next = computed(() => {
36
- return props.pagination.current_page + 1 < total_pages.value
37
- ? props.pagination.current_page + 1
38
+ return paginaAtual.value < total_pages.value
39
+ ? paginaAtual.value
38
40
  : null;
39
41
  });
40
42
 
@@ -44,19 +46,19 @@ const nextPage = (): void => {
44
46
  emit("tradePage");
45
47
  };
46
48
  const setPage = (newPage: number): void => {
47
- props.pagination.current_page = newPage - 1;
49
+ props.pagination.current_page = newPage + (props.page_starts_at - 1);
48
50
  emit("tradePage");
49
51
  };
50
52
  const lastPage = (): void => {
51
- props.pagination.current_page = total_pages.value - 1;
53
+ props.pagination.current_page = total_pages.value + (props.page_starts_at - 1);
52
54
  emit("tradePage");
53
55
  };
54
56
  const firstPage = (): void => {
55
- props.pagination.current_page = 0;
57
+ props.pagination.current_page = props.page_starts_at;
56
58
  emit("tradePage");
57
59
  };
58
60
  const prevPage = (): void => {
59
- if (props.pagination.current_page > 0) {
61
+ if (props.pagination.current_page > props.page_starts_at) {
60
62
  props.pagination.current_page--;
61
63
  emit("tradePage");
62
64
  }
@@ -65,7 +67,9 @@ const prevPage = (): void => {
65
67
  // =======================================================
66
68
  // 4. LÓGICA DE GERAÇÃO DE PÁGINAS
67
69
  // =======================================================
68
-
70
+ const paginaAtual = computed(() => {
71
+ return props.pagination.current_page - (props.page_starts_at - 1);
72
+ });
69
73
  /**
70
74
  * @description Computa um array com os números das páginas e as reticências a serem exibidas.
71
75
  * Ex: [1, 2, '...', 10, 11, 12, '...', 33, 34]
@@ -75,15 +79,15 @@ const paginasParaExibir = computed(() => {
75
79
  if (total_pages.value <= 7) {
76
80
  return Array.from({ length: total_pages.value }, (_, i) => i + 1);
77
81
  }
82
+
78
83
 
79
- const paginaAtual = props.pagination.current_page + 1;
80
84
  const total = total_pages.value;
81
85
 
82
86
  // O conjunto de páginas visíveis sempre inclui as 2 primeiras, 2 últimas,
83
87
  // a atual e suas duas vizinhas. O Set cuida de remover duplicatas.
84
88
  const paginasEssenciais = new Set([
85
89
  1, 2, // Sempre mostra as 2 primeiras
86
- paginaAtual - 1, paginaAtual, paginaAtual + 1, // Mostra a atual e vizinhas
90
+ paginaAtual.value - 1, paginaAtual.value, paginaAtual.value + 1, // Mostra a atual e vizinhas
87
91
  total - 1, total // Sempre mostra as 2 últimas
88
92
  ]);
89
93
 
@@ -133,27 +137,27 @@ const svg_uma_seta = `
133
137
  Mostrando de
134
138
  {{
135
139
  props.pagination.count !== 0
136
- ? props.pagination.limit_per_page * props.pagination.current_page + 1
140
+ ? props.pagination.limit_per_page * (paginaAtual - 1) + 1
137
141
  : 0
138
142
  }}
139
143
  até
140
144
  {{
141
- props.pagination.limit_per_page * (props.pagination.current_page + 1) < props.pagination.count ?
142
- props.pagination.limit_per_page * (props.pagination.current_page + 1) : props.pagination.count }} de {{
145
+ props.pagination.limit_per_page * (paginaAtual) < props.pagination.count ?
146
+ props.pagination.limit_per_page * (paginaAtual) : props.pagination.count }} de {{
143
147
  props.pagination.count }} registros </span>
144
148
  <div class="d-flex align-items-center gap-2" v-if="total_pages > 0">
145
149
  <div class="d-flex">
146
- <button class="btn btn-estilo" @click.prevent="firstPage" :disabled="props.pagination.current_page === 0" v-html="svg_duas_setas">
150
+ <button class="btn btn-estilo" @click.prevent="firstPage" :disabled="paginaAtual === 1" v-html="svg_duas_setas">
147
151
  </button>
148
- <button class="btn btn-estilo" @click.prevent="prevPage" :disabled="props.pagination.current_page === 0" v-html="svg_uma_seta">
152
+ <button class="btn btn-estilo" @click.prevent="prevPage" :disabled="paginaAtual === 1" v-html="svg_uma_seta">
149
153
  </button>
150
154
  </div>
151
155
 
152
156
  <div class="d-flex gap-2">
153
157
  <template v-for="(pagina, index) in paginasParaExibir" :key="index">
154
158
  <button v-if="typeof pagina === 'number'"
155
- :class="props.pagination.current_page + 1 == pagina ? 'page-select' : ''" class="page-estilo"
156
- @click.prevent="setPage(pagina)" :disabled="props.pagination.current_page + 1 == pagina">
159
+ :class="paginaAtual == pagina ? 'page-select' : ''" class="page-estilo"
160
+ @click.prevent="setPage(pagina)" :disabled="paginaAtual == pagina">
157
161
  {{ pagina }}
158
162
  </button>
159
163
  <span v-else class="m-0 p-0">...</span>
@@ -15,9 +15,14 @@
15
15
  <input type="text" class="form-control ms-1" id="inputSearchLaudos" v-model="modelSearch"
16
16
  @keyup.enter="$emit('search')" placeholder="Buscar...">
17
17
 
18
- <span v-if="modelSearch" @click="cleanSearch()" class=" inputClose"
19
- title="Limpar pesquisa">
20
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-x"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M18 6l-12 12" /><path d="M6 6l12 12" /></svg>
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>
21
26
  </span>
22
27
  <span v-else class="input-icon-addon">
23
28
  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
@@ -30,19 +35,24 @@
30
35
  </div>
31
36
  <div v-if="item_use.includes(2)" class="dropdown-menu">
32
37
  <template v-for="(filter, index) in props.list_filter" :key="index">
33
- <router-link v-if="'type' in filter ? filter.type === 1 : false" :to="filter?.to"
38
+ <router-link v-if="('type' in filter ? filter.type === 1 : false) && ('visible' in filter ? filter.visible : true)" :to="filter?.to"
34
39
  class="dropdown-item cursor-pointer">
35
40
  {{ filter.text }}
36
41
  </router-link>
37
- <a v-else-if="'type' in filter ? filter.type === 2 : true"
42
+ <a v-else-if="('type' in filter ? filter.type === 2 : true) && ('visible' in filter ? filter.visible : true)"
38
43
  @click.prevent="tradeFilter(String(filter.value))" class="dropdown-item cursor-pointer"
39
44
  :class="modelFilter === filter?.value ? 'bg-info text-dark selected' : ''">
40
45
  {{ filter.text }}
41
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>
42
53
  </template>
43
54
  </div>
44
55
  </div>
45
-
46
56
  </template>
47
57
  <script setup lang="ts">
48
58
  import { computed, watch } from 'vue';
@@ -83,6 +93,8 @@ const props = withDefaults(defineProps<SearchProps>(), {
83
93
  // até o momento existem 2 items: 1 (search) e 2 (filter)
84
94
  item_use: () => [1, 2], // se não for passado, assume que é para todos os itens
85
95
  // até o momento existem 2 items: 1 (search) e 2 (filter)
96
+
97
+ click: null,
86
98
  });
87
99
 
88
100
  const emit = defineEmits(['update:search', 'update:filter', "search"])
@@ -128,10 +140,20 @@ function tradeFilter(newFilter: string): void {
128
140
  modelFilter.value = newFilter;
129
141
  }
130
142
  }
143
+
131
144
  function cleanSearch(): void {
132
145
  modelSearch.value = ""
133
146
  emit('search'); // emite o evento de busca para atualizar a lista
134
147
  }
148
+
149
+ function clickItem(filter: any): void {
150
+ if (filter.click && typeof filter.click === 'function') {
151
+ filter.click();
152
+ } else {
153
+ console.error("O filtro selecionado não possui uma função de clique válida.");
154
+ }
155
+ }
156
+
135
157
  </script>
136
158
  <style scoped>
137
159
  .inputClose {
@@ -20,6 +20,10 @@ interface VColumnProps {
20
20
  click?: Function | null;
21
21
  // bloqueia a coluna para não ser movida
22
22
  locked?: boolean;
23
+ use_ordering?: boolean;
24
+ param_ordering?: string;
25
+ decreasing_value?: string;
26
+ increasing_value?: string;
23
27
  }
24
28
  const props = withDefaults(defineProps<VColumnProps>(), {
25
29
  field: null,
@@ -46,6 +50,11 @@ const props = withDefaults(defineProps<VColumnProps>(), {
46
50
  transform_function: null ,
47
51
  click: null,
48
52
  locked: false,
53
+
54
+ use_ordering: false,
55
+ param_ordering: '',
56
+ decreasing_value: '',
57
+ increasing_value: '',
49
58
  });
50
59
 
51
60
  const slots = useSlots();
@@ -91,6 +100,10 @@ onMounted(() => {
91
100
  click: props.click,
92
101
  transform_function: props.transform_function,
93
102
  locked: props.locked,
103
+ use_ordering: props.use_ordering,
104
+ param_ordering: props.param_ordering,
105
+ decreasing_value: props.decreasing_value,
106
+ increasing_value: props.increasing_value,
94
107
 
95
108
  bodySlot: slots.body,
96
109
  ...(props.type === 'text' && { limite_text: Number(props.limite_text) }),
@@ -23,12 +23,16 @@
23
23
  <Search v-model:search="pagination.search" v-model:filter="pagination.filter" :list_filter="props.list_filter"
24
24
  :item_use="item_use" @search="reSearch" />
25
25
  </div>
26
+ <slot name="item-selected-info" :selected_items="selected_items" :clearSelection="() => selected_items = []">
27
+ <div v-if="(props.use_checkbox && selected_items.length > 0) && !props.deactivate_selected_info"
28
+ class="alert alert-cyan d-flex justify-content-center align-items-center py-2" role="alert">
29
+ <h4 class="alert-title m-0"> <strong>Itens Selecionados:</strong> <span class="badge bg-azure text-azure-fg">{{ selected_items.length }}</span></h4>
30
+ <a class=" cursor-pointer " @click="selected_items = []">
31
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-trash"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 7l16 0" /><path d="M10 11l0 6" /><path d="M14 11l0 6" /><path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12" /><path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3" /></svg>
32
+ Limpar Seleção</a>
33
+ </div>
34
+ </slot>
26
35
 
27
- <div v-if="props.use_checkbox && selected_items.length > 0"
28
- class="alert alert-cyan d-flex justify-content-center align-items-center py-3" role="alert">
29
- <h4 class="alert-title m-0"> <strong>Itens Selecionados:</strong> {{ selected_items.length }}</h4>
30
- <button class="btn btn-outline-danger ms-3 bold " @click="selected_items = []">Limpar Seleção</button>
31
- </div>
32
36
  <template v-if="showLoadingState">
33
37
  <template v-if="props.custom_loading">
34
38
  <component :is="props.custom_loading" />
@@ -156,16 +160,110 @@
156
160
  </template>
157
161
 
158
162
  <template #item="{ element: col }">
159
- <th class="header-draggable" :class="col.class_column">
160
- {{ col.header }}
161
- </th>
163
+ <template v-if="col.use_ordering">
164
+ <th class="header-draggable" :class="col.class_column">
165
+ <div class="header-ordering">
166
+ <span>{{ col.header }}</span>
167
+
168
+ <span @click="() => toggleOrderingState(col.header)" class="ms-2 cursor-pointer">
169
+ <svg v-if="!orderings_state[col.header] || orderings_state[col.header] === 'none'"
170
+ xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
171
+ stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
172
+ <path d="m3 8 4-4 4 4"></path>
173
+ <path d="m11 16-4 4-4-4"></path>
174
+ <path d="M7 4v16"></path>
175
+ <path d="M15 8h6"></path>
176
+ <path d="M15 16h6"></path>
177
+ <path d="M13 12h8"></path>
178
+ </svg>
179
+
180
+
181
+ <svg v-else-if="orderings_state[col.header] === 'decreasing'"
182
+ xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
183
+ stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
184
+ <path d="m3 16 4 4 4-4"></path>
185
+ <path d="M7 20V4"></path>
186
+ <path d="M11 4h10"></path>
187
+ <path d="M11 8h7"></path>
188
+ <path d="M11 12h4"></path>
189
+ </svg>
190
+
191
+ <svg v-else-if="orderings_state[col.header] === 'increasing'"
192
+ xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
193
+ stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
194
+ <path d="m3 8 4-4 4 4"></path>
195
+ <path d="M7 4v16"></path>
196
+ <path d="M11 12h4"></path>
197
+ <path d="M11 16h7"></path>
198
+ <path d="M11 20h10"></path>
199
+ </svg>
200
+
201
+ </span>
202
+ </div>
203
+
204
+ </th>
205
+ </template>
206
+ <template v-else>
207
+ <th class="header-draggable" :class="col.class_column">
208
+ {{ col.header }}
209
+ </th>
210
+ </template>
211
+
162
212
  </template>
163
213
 
164
214
  <template #footer>
165
215
  <template v-for="col in lockedColumns" :key="col.field || col.header">
166
- <th class="header-locked" :class="col.class_column">
167
- {{ col.header }}
168
- </th>
216
+ <template v-if="col.use_ordering">
217
+ <th class="header-locked header-ordering" :class="col.class_column">
218
+ <div class="header-ordering">
219
+ <span>{{ col.header }}</span>
220
+
221
+ <span @click="() => toggleOrderingState(col.header)" class="ms-2 cursor-pointer">
222
+ <svg v-if="!orderings_state[col.header] || orderings_state[col.header] === 'none'"
223
+ xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
224
+ fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
225
+ stroke-linejoin="round">
226
+ <path d="m3 8 4-4 4 4"></path>
227
+ <path d="m11 16-4 4-4-4"></path>
228
+ <path d="M7 4v16"></path>
229
+ <path d="M15 8h6"></path>
230
+ <path d="M15 16h6"></path>
231
+ <path d="M13 12h8"></path>
232
+ </svg>
233
+
234
+
235
+ <svg v-else-if="orderings_state[col.header] === 'decreasing'"
236
+ xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
237
+ fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
238
+ stroke-linejoin="round">
239
+ <path d="m3 16 4 4 4-4"></path>
240
+ <path d="M7 20V4"></path>
241
+ <path d="M11 4h10"></path>
242
+ <path d="M11 8h7"></path>
243
+ <path d="M11 12h4"></path>
244
+ </svg>
245
+
246
+ <svg v-else-if="orderings_state[col.header] === 'increasing'"
247
+ xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
248
+ fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
249
+ stroke-linejoin="round">
250
+ <path d="m3 8 4-4 4 4"></path>
251
+ <path d="M7 4v16"></path>
252
+ <path d="M11 12h4"></path>
253
+ <path d="M11 16h7"></path>
254
+ <path d="M11 20h10"></path>
255
+ </svg>
256
+ </span>
257
+ </div>
258
+ </th>
259
+ </template>
260
+
261
+ <template v-else>
262
+ <th class="header-locked" :class="col.class_column">
263
+ {{ col.header }}
264
+ </th>
265
+ </template>
266
+
169
267
  </template>
170
268
  </template>
171
269
  </draggable>
@@ -188,10 +286,10 @@
188
286
  <span @click="col.click ? col.click(item) : null" v-else-if="col.type === 'date'"
189
287
  :class="col.class_item + (col.click ? ' cursor-pointer' : '')">
190
288
  <span v-if="col.format === 'complete'">{{ new Date(getSubItem(col.field, item)).toLocaleString()
191
- }}</span>
289
+ }}</span>
192
290
  <span v-if="col.format === 'simple'"> {{ new Date(getSubItem(col.field,
193
291
  item)).toLocaleDateString()
194
- }} </span>
292
+ }} </span>
195
293
  </span>
196
294
  <div @click="col.click ? col.click(item) : null"
197
295
  :class="col.class_item + (col.click ? ' cursor-pointer' : '')" v-else-if="col.type === 'html'"
@@ -232,7 +330,8 @@
232
330
  </div>
233
331
  <slot name="pagination" :pagination="pagination" :tradePage="fetchDataWithDelay" :error="error">
234
332
  <div v-if="!error && pagination.count > 0" class="px-3" :class="props.class_pagination">
235
- <PaginationDatatable :filtering="true" :pagination="pagination" @tradePage="fetchDataWithDelay" />
333
+ <PaginationDatatable :page_starts_at="props.page_starts_at" :filtering="true" :pagination="pagination"
334
+ @tradePage="fetchDataWithDelay" />
236
335
  </div>
237
336
  </slot>
238
337
 
@@ -316,10 +415,14 @@ interface VDataTableProps {
316
415
  item_key?: string;
317
416
 
318
417
  limit_per_page?: number;
418
+ page_starts_at?: number;
419
+ deactivate_selected_info?: boolean;
420
+
319
421
  }
320
422
 
321
423
  interface ExposedFunctions {
322
424
  execute: () => void;
425
+ reSearch: () => void;
323
426
  pagination: Ref<PaginationObject>;
324
427
  default_params: Record<string, any>;
325
428
  selected_items: Ref<T[]>;
@@ -359,6 +462,8 @@ const props = withDefaults(defineProps<VDataTableProps>(), {
359
462
  first_text_page_size: 'Mostrar',
360
463
  second_text_page_size: 'registros',
361
464
  limit_per_page: 5,
465
+ page_starts_at: 0,
466
+ deactivate_selected_info: false,
362
467
  });
363
468
 
364
469
 
@@ -367,6 +472,7 @@ const props = withDefaults(defineProps<VDataTableProps>(), {
367
472
  // =======================================================
368
473
 
369
474
 
475
+ const orderings_state = ref<Record<string, 'none' | 'increasing' | 'decreasing'>>({});
370
476
  const columns = ref<ColumnConfiguration[]>([]);
371
477
  const items = ref<T[]>([]) as Ref<T[]>;
372
478
  const totalItems = ref<number>(0);
@@ -378,36 +484,45 @@ const delayTimer = ref<ReturnType<typeof setTimeout> | null>(null);
378
484
 
379
485
  /*--------- definição de páginação ---------------*/
380
486
  const pagination = ref<PaginationObject>({
381
- current_page: 0, // pagina atual
487
+ current_page: props.page_starts_at, // pagina atual
382
488
  count: 0, // total de itens
383
489
  limit_per_page: props.limit_per_page, // limite de itens por página
384
490
  search: '', // termo de busca
385
491
  filter: '', // filtro selecionado
386
492
  })
387
493
 
494
+ const urlReativa = computed(() => {
495
+ pagination.value.current_page = props.page_starts_at;
496
+ return props.endpoint;
497
+ });
388
498
  // =======================================================
389
499
  // 3. LÓGICA DA API (useFetch)
390
500
  // =======================================================
391
- const { data: response, pending, error, execute, attempt } = props.fetch(props.endpoint, {
501
+ const { data: response, pending, error, execute, attempt } = props.fetch(urlReativa, {
392
502
  params: () => {
393
-
394
503
  if (props.deactivate_default_params) {
395
504
  if (props.add_params && typeof props.add_params === 'function') {
396
- return props.add_params();
505
+ return {
506
+ ...props.add_params(),
507
+ ...params_ordering.value
508
+ };
397
509
  }
398
510
  return {
399
511
  ...props.add_params,
512
+ ...params_ordering.value
400
513
  };
401
514
  }
402
515
  else if (props.add_params && typeof props.add_params === 'function') {
403
516
  return {
404
517
  ...default_params.value,
405
518
  ...props.add_params(),
519
+ ...params_ordering.value
406
520
  }
407
521
  }
408
522
  return {
409
523
  ...default_params.value,
410
524
  ...props.add_params,
525
+ ...params_ordering.value
411
526
  };
412
527
  },
413
528
  retry: props.retry_attempts,
@@ -446,6 +561,24 @@ const item_use = computed<number[]>(() => {
446
561
  }
447
562
  return use;
448
563
  });
564
+ const params_ordering = computed(() => {
565
+ const objectOrdering: Record<string, any> = {};
566
+ for (const col of columns.value) {
567
+ if (col.use_ordering) {
568
+ if (orderings_state.value[col.header] === 'increasing') {
569
+ objectOrdering[col.param_ordering] = col.increasing_value || 'increasing';
570
+ } else if (orderings_state.value[col.header] === 'decreasing') {
571
+ objectOrdering[col.param_ordering] = col.decreasing_value || 'decreasing';
572
+ } else {
573
+ continue;
574
+ }
575
+ } else {
576
+ continue;
577
+ }
578
+ }
579
+
580
+ return objectOrdering;
581
+ });
449
582
 
450
583
  const default_params = computed<Record<string, any>>(() => ({
451
584
  [props.page_param_name]: pagination.value.current_page + 1,
@@ -573,7 +706,7 @@ function fetchDataWithDelay(): void {
573
706
  }
574
707
 
575
708
  function reSearch(): void {
576
- pagination.value.current_page = 0;
709
+ pagination.value.current_page = props.page_starts_at;
577
710
  fetchDataWithDelay();
578
711
  }
579
712
 
@@ -583,8 +716,7 @@ const changePageSize = (event: Event): void => {
583
716
  if (newSize > 0) {
584
717
  pagination.value.limit_per_page = newSize;
585
718
  pagination.value.limit_per_page = newSize; // Atualiza o limite de itens por página
586
- pagination.value.current_page = 0;
587
- fetchDataWithDelay();
719
+ reSearch();
588
720
  }
589
721
  };
590
722
 
@@ -617,32 +749,49 @@ function limiteText(text: string | null, limite: number | null): string | null {
617
749
  return text;
618
750
  }
619
751
 
752
+ function toggleOrderingState(header: string) {
753
+ // desabilita todos que não são o header clicado
754
+ for (const key in orderings_state.value) {
755
+ if (key !== header) {
756
+ orderings_state.value[key] = 'none';
757
+ }
758
+ }
759
+
760
+ const currentState = orderings_state.value[header] || 'none';
761
+ if (currentState === 'none') {
762
+ orderings_state.value[header] = 'increasing';
763
+ } else if (currentState === 'increasing') {
764
+ orderings_state.value[header] = 'decreasing';
765
+ } else {
766
+ orderings_state.value[header] = 'none';
767
+ }
768
+
769
+ reSearch();
770
+ }
771
+
772
+
620
773
  // =======================================================
621
774
  // 7. EXPOSE E CICLO DE VIDA
622
775
  // =======================================================
623
776
  function set_limit_per_page(newLimit: number): void {
624
777
  if (newLimit > 0) {
625
778
  pagination.value.limit_per_page = newLimit;
626
- pagination.value.current_page = 0;
627
- fetchDataWithDelay();
779
+ reSearch();
628
780
  } else {
629
781
  console.warn("O limite deve ser um número maior que zero.");
630
782
  }
631
783
  }
632
784
  function set_search(newSearch: string): void {
633
785
  pagination.value.search = newSearch;
634
- pagination.value.current_page = 0;
635
- fetchDataWithDelay();
786
+ reSearch();
636
787
  }
637
788
  function set_filter(newFilter: string): void {
638
789
  pagination.value.filter = newFilter;
639
- pagination.value.current_page = 0;
640
- fetchDataWithDelay();
790
+ reSearch();
641
791
  }
642
792
  function set_page(newPage: number): void {
643
- if (newPage >= 1 && newPage <= Math.ceil(pagination.value.count / pagination.value.limit_per_page)) {
644
- pagination.value.current_page = newPage - 1;
645
- fetchDataWithDelay();
793
+ if (newPage >= 0 && newPage <= Math.ceil(pagination.value.count / pagination.value.limit_per_page)) {
794
+ reSearch();
646
795
  } else {
647
796
  console.warn("Número de página inválido.");
648
797
  }
@@ -652,6 +801,7 @@ defineExpose<
652
801
  ExposedFunctions
653
802
  >({
654
803
  execute: fetchDataWithDelay,
804
+ reSearch:reSearch,
655
805
  pagination: readonly(pagination),
656
806
  set_limit_per_page: set_limit_per_page,
657
807
  set_search: set_search,
@@ -835,6 +985,7 @@ $max-width-preview: 250px;
835
985
  cursor: grab;
836
986
  }
837
987
 
988
+
838
989
  /*
839
990
  Animações para movimentação de colunas
840
991
  */
@@ -846,4 +997,10 @@ $max-width-preview: 250px;
846
997
  .column-move-leave-active {
847
998
  transition: all 0.4s ease;
848
999
  }
1000
+
1001
+
1002
+ .header-ordering {
1003
+ display: flex;
1004
+ justify-content: space-between;
1005
+ }
849
1006
  </style>
@@ -15,6 +15,10 @@ export interface ColumnConfiguration {
15
15
  format?: 'complete' | 'simple';
16
16
  click: Function | null;
17
17
  locked: boolean;
18
+ use_ordering: boolean;
19
+ param_ordering: string;
20
+ decreasing_value: string;
21
+ increasing_value: string;
18
22
  }
19
23
 
20
24
  // A API que o VDataTable "fornece" para os filhos