v-sistec-features 1.4.1 → 1.6.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "v-sistec-features",
3
3
  "private": false,
4
- "version": "1.4.1",
4
+ "version": "1.6.0",
5
5
  "author": "Márlon Bento Azevedo (https://github.com/marlon-bento)",
6
6
  "repository": {
7
7
  "type": "git",
@@ -45,7 +45,8 @@
45
45
  },
46
46
  "peerDependencies": {
47
47
  "@tabler/icons-vue": "^3.35.0",
48
- "vue": "^3.2.0"
48
+ "vue": "^3.2.0",
49
+ "vuedraggable": "^4.1.0"
49
50
  },
50
51
  "devDependencies": {
51
52
  "@semantic-release/changelog": "^6.0.3",
@@ -65,8 +66,8 @@
65
66
  "vue-tsc": "^3.1.0"
66
67
  },
67
68
  "dependencies": {
69
+ "@formkit/drag-and-drop": "^0.5.3",
68
70
  "@tabler/core": "^1.4.0",
69
- "v3-infinite-loading": "^1.3.2",
70
- "vue": "^3.2.0"
71
+ "v3-infinite-loading": "^1.3.2"
71
72
  }
72
73
  }
@@ -18,6 +18,12 @@ interface VColumnProps {
18
18
  limite_text?: number | string | null;
19
19
  transform_function?: ((value: any) => any) | null;
20
20
  click?: Function | null;
21
+ // bloqueia a coluna para não ser movida
22
+ locked?: boolean;
23
+ use_ordering?: boolean;
24
+ param_ordering?: string;
25
+ decreasing_value?: string;
26
+ increasing_value?: string;
21
27
  }
22
28
  const props = withDefaults(defineProps<VColumnProps>(), {
23
29
  field: null,
@@ -43,6 +49,12 @@ const props = withDefaults(defineProps<VColumnProps>(), {
43
49
  /* recebe função para alterar o que é mostrado */
44
50
  transform_function: null ,
45
51
  click: null,
52
+ locked: false,
53
+
54
+ use_ordering: false,
55
+ param_ordering: '',
56
+ decreasing_value: '',
57
+ increasing_value: '',
46
58
  });
47
59
 
48
60
  const slots = useSlots();
@@ -87,6 +99,11 @@ onMounted(() => {
87
99
  class_item: props.class_item,
88
100
  click: props.click,
89
101
  transform_function: props.transform_function,
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,
90
107
 
91
108
  bodySlot: slots.body,
92
109
  ...(props.type === 'text' && { limite_text: Number(props.limite_text) }),
@@ -136,7 +136,7 @@
136
136
  <div v-if="items.length > 0">
137
137
  <table class="table table-vcenter table-selectable" :class="props.class_table">
138
138
  <thead>
139
- <tr>
139
+ <!-- <tr>
140
140
  <th v-if="props.use_checkbox" class="w-1">
141
141
  <input class="form-check-input m-0" type="checkbox" ref="selectAllCheckbox"
142
142
  @change="toggleSelectAll" aria-label="Selecionar todos os itens na página" />
@@ -144,15 +144,134 @@
144
144
  <th v-for="col in columns" :key="col.field || col.header" :class="col.class_column">
145
145
  {{ col.header }}
146
146
  </th>
147
- </tr>
147
+ </tr> -->
148
+
149
+ <draggable v-model="draggableColumns" tag="tr" item-key="header" :animation="400"
150
+ ghost-class="ghost-item" drag-class="dragging-item">
151
+ <template #header>
152
+ <th v-if="props.use_checkbox" class="w-1">
153
+ <input class="form-check-input m-0" type="checkbox" ref="selectAllCheckbox"
154
+ @change="toggleSelectAll" aria-label="Selecionar todos os itens na página" />
155
+ </th>
156
+ </template>
157
+
158
+ <template #item="{ element: col }">
159
+ <template v-if="col.use_ordering">
160
+ <th class="header-draggable" :class="col.class_column">
161
+ <div class="header-ordering">
162
+ <span>{{ col.header }}</span>
163
+
164
+ <span @click="() => toggleOrderingState(col.header)" class="ms-2 cursor-pointer">
165
+ <svg v-if="!orderings_state[col.header] || orderings_state[col.header] === 'none'"
166
+ xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
167
+ stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
168
+ <path d="m3 8 4-4 4 4"></path>
169
+ <path d="m11 16-4 4-4-4"></path>
170
+ <path d="M7 4v16"></path>
171
+ <path d="M15 8h6"></path>
172
+ <path d="M15 16h6"></path>
173
+ <path d="M13 12h8"></path>
174
+ </svg>
175
+
176
+
177
+ <svg v-else-if="orderings_state[col.header] === 'decreasing'"
178
+ xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
179
+ stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
180
+ <path d="m3 16 4 4 4-4"></path>
181
+ <path d="M7 20V4"></path>
182
+ <path d="M11 4h10"></path>
183
+ <path d="M11 8h7"></path>
184
+ <path d="M11 12h4"></path>
185
+ </svg>
186
+
187
+ <svg v-else-if="orderings_state[col.header] === 'increasing'"
188
+ xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
189
+ stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
190
+ <path d="m3 8 4-4 4 4"></path>
191
+ <path d="M7 4v16"></path>
192
+ <path d="M11 12h4"></path>
193
+ <path d="M11 16h7"></path>
194
+ <path d="M11 20h10"></path>
195
+ </svg>
196
+
197
+ </span>
198
+ </div>
199
+
200
+ </th>
201
+ </template>
202
+ <template v-else>
203
+ <th class="header-draggable" :class="col.class_column">
204
+ {{ col.header }}
205
+ </th>
206
+ </template>
207
+
208
+ </template>
209
+
210
+ <template #footer>
211
+ <template v-for="col in lockedColumns" :key="col.field || col.header">
212
+ <template v-if="col.use_ordering">
213
+ <th class="header-locked header-ordering" :class="col.class_column">
214
+ <div class="header-ordering">
215
+ <span>{{ col.header }}</span>
216
+
217
+ <span @click="() => toggleOrderingState(col.header)" class="ms-2 cursor-pointer">
218
+ <svg v-if="!orderings_state[col.header] || orderings_state[col.header] === 'none'"
219
+ xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
220
+ fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
221
+ stroke-linejoin="round">
222
+ <path d="m3 8 4-4 4 4"></path>
223
+ <path d="m11 16-4 4-4-4"></path>
224
+ <path d="M7 4v16"></path>
225
+ <path d="M15 8h6"></path>
226
+ <path d="M15 16h6"></path>
227
+ <path d="M13 12h8"></path>
228
+ </svg>
229
+
230
+
231
+ <svg v-else-if="orderings_state[col.header] === 'decreasing'"
232
+ xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
233
+ fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
234
+ stroke-linejoin="round">
235
+ <path d="m3 16 4 4 4-4"></path>
236
+ <path d="M7 20V4"></path>
237
+ <path d="M11 4h10"></path>
238
+ <path d="M11 8h7"></path>
239
+ <path d="M11 12h4"></path>
240
+ </svg>
241
+
242
+ <svg v-else-if="orderings_state[col.header] === 'increasing'"
243
+ xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
244
+ fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
245
+ stroke-linejoin="round">
246
+ <path d="m3 8 4-4 4 4"></path>
247
+ <path d="M7 4v16"></path>
248
+ <path d="M11 12h4"></path>
249
+ <path d="M11 16h7"></path>
250
+ <path d="M11 20h10"></path>
251
+ </svg>
252
+ </span>
253
+ </div>
254
+ </th>
255
+ </template>
256
+
257
+ <template v-else>
258
+ <th class="header-locked" :class="col.class_column">
259
+ {{ col.header }}
260
+ </th>
261
+ </template>
262
+
263
+ </template>
264
+ </template>
265
+ </draggable>
266
+
148
267
  </thead>
149
268
  <tbody>
150
- <tr v-for="item in items" :key="item[props.item_key]">
269
+ <TransitionGroup tag="tr" v-for="item in items" :key="item[props.item_key]" name="column-move">
151
270
  <td v-if="props.use_checkbox" class="w-1">
152
271
  <input class="form-check-input m-0" type="checkbox" :checked="isSelected(item)"
153
272
  @change="toggleItemSelection(item)" aria-label="Selecionar este item" />
154
273
  </td>
155
- <td v-for="col in columns" :key="col.field || col.header" :class="col.class_row">
274
+ <td v-for="col in renderedColumns" :key="col.field || col.header" :class="col.class_row">
156
275
  <component v-if="col.bodySlot" :is="col.bodySlot" :item="item" :is-selected="isSelected(item)" />
157
276
  <span @click="col.click ? col.click(item) : null"
158
277
  :class="col.class_item + (col.click ? ' cursor-pointer' : '')" v-else-if="col.type === 'text'">
@@ -163,10 +282,10 @@
163
282
  <span @click="col.click ? col.click(item) : null" v-else-if="col.type === 'date'"
164
283
  :class="col.class_item + (col.click ? ' cursor-pointer' : '')">
165
284
  <span v-if="col.format === 'complete'">{{ new Date(getSubItem(col.field, item)).toLocaleString()
166
- }}</span>
285
+ }}</span>
167
286
  <span v-if="col.format === 'simple'"> {{ new Date(getSubItem(col.field,
168
287
  item)).toLocaleDateString()
169
- }} </span>
288
+ }} </span>
170
289
  </span>
171
290
  <div @click="col.click ? col.click(item) : null"
172
291
  :class="col.class_item + (col.click ? ' cursor-pointer' : '')" v-else-if="col.type === 'html'"
@@ -195,7 +314,7 @@
195
314
  <span class="text-danger erro-custom-container" v-else>tipo <span
196
315
  class="badge bg-orange text-white erro-custom-text">{{ col.type }}</span> não suportado</span>
197
316
  </td>
198
- </tr>
317
+ </TransitionGroup>
199
318
  </tbody>
200
319
  </table>
201
320
  </div>
@@ -203,10 +322,7 @@
203
322
  <p class="m-0">Nenhum item encontrado.</p>
204
323
  </div>
205
324
  </div>
206
-
207
325
  </div>
208
-
209
-
210
326
  </div>
211
327
  <slot name="pagination" :pagination="pagination" :tradePage="fetchDataWithDelay" :error="error">
212
328
  <div v-if="!error && pagination.count > 0" class="px-3" :class="props.class_pagination">
@@ -228,6 +344,7 @@ import PaginationDatatable from './PaginationDatatable.vue';
228
344
  import Search from './SearchDatatable.vue';
229
345
  import { useImagePreview } from '../composables/useImagePreview';
230
346
  import { dataTableApiKey, type ColumnConfiguration, type PaginationObject } from '../keys';
347
+ import draggable from 'vuedraggable';
231
348
 
232
349
  const {
233
350
  isHovering,
@@ -344,6 +461,7 @@ const props = withDefaults(defineProps<VDataTableProps>(), {
344
461
  // =======================================================
345
462
 
346
463
 
464
+ const orderings_state = ref<Record<string, 'none' | 'increasing' | 'decreasing'>>({});
347
465
  const columns = ref<ColumnConfiguration[]>([]);
348
466
  const items = ref<T[]>([]) as Ref<T[]>;
349
467
  const totalItems = ref<number>(0);
@@ -367,24 +485,29 @@ const pagination = ref<PaginationObject>({
367
485
  // =======================================================
368
486
  const { data: response, pending, error, execute, attempt } = props.fetch(props.endpoint, {
369
487
  params: () => {
370
-
371
488
  if (props.deactivate_default_params) {
372
489
  if (props.add_params && typeof props.add_params === 'function') {
373
- return props.add_params();
490
+ return {
491
+ ...props.add_params(),
492
+ ...params_ordering.value
493
+ };
374
494
  }
375
495
  return {
376
496
  ...props.add_params,
497
+ ...params_ordering.value
377
498
  };
378
499
  }
379
500
  else if (props.add_params && typeof props.add_params === 'function') {
380
501
  return {
381
502
  ...default_params.value,
382
503
  ...props.add_params(),
504
+ ...params_ordering.value
383
505
  }
384
506
  }
385
507
  return {
386
508
  ...default_params.value,
387
509
  ...props.add_params,
510
+ ...params_ordering.value
388
511
  };
389
512
  },
390
513
  retry: props.retry_attempts,
@@ -396,6 +519,26 @@ const { data: response, pending, error, execute, attempt } = props.fetch(props.e
396
519
  // =======================================================
397
520
  // 4. PROPRIEDADES COMPUTADAS
398
521
  // =======================================================
522
+
523
+ // colunas TRAVADAS (apenas leitura)
524
+ const lockedColumns = computed(() =>
525
+ columns.value.filter(c => c.locked)
526
+ );
527
+ // 'v-model' para as colunas ARRASTÁVEIS (com get/set)
528
+ const draggableColumns = computed({
529
+ get() {
530
+ return columns.value.filter(c => !c.locked);
531
+ },
532
+ set(newUnlockedOrder) {
533
+ const locked = lockedColumns.value;
534
+ columns.value = [...newUnlockedOrder, ...locked];
535
+ }
536
+ });
537
+ // colunas RENDERIZADAS (ordem final)
538
+ const renderedColumns = computed(() => {
539
+ return [...draggableColumns.value, ...lockedColumns.value];
540
+ });
541
+
399
542
  const item_use = computed<number[]>(() => {
400
543
  let use = [1]
401
544
  if (props.list_filter.length > 0) {
@@ -403,6 +546,24 @@ const item_use = computed<number[]>(() => {
403
546
  }
404
547
  return use;
405
548
  });
549
+ const params_ordering = computed(() => {
550
+ const objectOrdering: Record<string, any> = {};
551
+ for (const col of columns.value) {
552
+ if (col.use_ordering) {
553
+ if (orderings_state.value[col.header] === 'increasing') {
554
+ objectOrdering[col.param_ordering] = col.increasing_value || 'increasing';
555
+ } else if (orderings_state.value[col.header] === 'decreasing') {
556
+ objectOrdering[col.param_ordering] = col.decreasing_value || 'decreasing';
557
+ } else {
558
+ continue;
559
+ }
560
+ } else {
561
+ continue;
562
+ }
563
+ }
564
+
565
+ return objectOrdering;
566
+ });
406
567
 
407
568
  const default_params = computed<Record<string, any>>(() => ({
408
569
  [props.page_param_name]: pagination.value.current_page + 1,
@@ -574,6 +735,27 @@ function limiteText(text: string | null, limite: number | null): string | null {
574
735
  return text;
575
736
  }
576
737
 
738
+ function toggleOrderingState(header: string) {
739
+ // desabilita todos que não são o header clicado
740
+ for (const key in orderings_state.value) {
741
+ if (key !== header) {
742
+ orderings_state.value[key] = 'none';
743
+ }
744
+ }
745
+
746
+ const currentState = orderings_state.value[header] || 'none';
747
+ if (currentState === 'none') {
748
+ orderings_state.value[header] = 'increasing';
749
+ } else if (currentState === 'increasing') {
750
+ orderings_state.value[header] = 'decreasing';
751
+ } else {
752
+ orderings_state.value[header] = 'none';
753
+ }
754
+
755
+ reSearch();
756
+ }
757
+
758
+
577
759
  // =======================================================
578
760
  // 7. EXPOSE E CICLO DE VIDA
579
761
  // =======================================================
@@ -766,4 +948,48 @@ $max-width-preview: 250px;
766
948
  [data-bs-theme=dark] .form-check-input {
767
949
  border-color: rgba(255, 255, 255, 0.374) !important;
768
950
  }
951
+
952
+
953
+
954
+ /*
955
+ Estilos para arrastar e soltar colunas
956
+ */
957
+
958
+ .ghost-item {
959
+ opacity: 0.5;
960
+ background: var(--tblr-primary-lt, #e6f0ff);
961
+ border-radius: 8px;
962
+ }
963
+
964
+ .dragging-item {
965
+ cursor: grabbing;
966
+ background: var(--tblr-primary);
967
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
968
+ opacity: 0.5;
969
+ background-color: var(--tblr-primary-bg-subtle);
970
+ filter: grayscale(0) invert(0);
971
+ }
972
+
973
+ .header-draggable {
974
+ cursor: grab;
975
+ }
976
+
977
+
978
+ /*
979
+ Animações para movimentação de colunas
980
+ */
981
+ .column-move-move {
982
+ transition: transform 0.4s ease;
983
+ }
984
+
985
+ .column-move-enter-active,
986
+ .column-move-leave-active {
987
+ transition: all 0.4s ease;
988
+ }
989
+
990
+
991
+ .header-ordering {
992
+ display: flex;
993
+ justify-content: space-between;
994
+ }
769
995
  </style>
@@ -14,6 +14,11 @@ export interface ColumnConfiguration {
14
14
  deactivate_img_preview?: boolean;
15
15
  format?: 'complete' | 'simple';
16
16
  click: Function | null;
17
+ locked: boolean;
18
+ use_ordering: boolean;
19
+ param_ordering: string;
20
+ decreasing_value: string;
21
+ increasing_value: string;
17
22
  }
18
23
 
19
24
  // A API que o VDataTable "fornece" para os filhos