@innertia-solutions/nuxt-theme-spark 0.1.32 → 0.1.34
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/components/Table/Standard.vue +195 -85
- package/components/Table.vue +4 -90
- package/package.json +1 -1
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { IconSearch, IconAdjustmentsHorizontal, IconLayoutColumns } from '@tabler/icons-vue'
|
|
2
|
+
import { IconSearch, IconAdjustmentsHorizontal, IconLayoutColumns, IconGripVertical, IconBolt, IconReload } from '@tabler/icons-vue'
|
|
3
3
|
|
|
4
|
-
// Standard admin table: search + filters + Table (TanStack) + export
|
|
5
4
|
const props = defineProps({
|
|
6
5
|
endpoint: { type: String, required: true },
|
|
7
6
|
columns: { type: Array, required: true },
|
|
@@ -32,12 +31,52 @@ const activeFilterCount = computed(() =>
|
|
|
32
31
|
Object.values(filters.value).filter(v => v !== null && v !== undefined && v !== '').length
|
|
33
32
|
)
|
|
34
33
|
|
|
35
|
-
// Merge filters into params
|
|
36
34
|
const mergedParams = computed(() => ({
|
|
37
35
|
...props.params,
|
|
38
36
|
...filters.value,
|
|
39
37
|
}))
|
|
40
38
|
|
|
39
|
+
// ─── Column panel ─────────────────────────────────────────────────────────────
|
|
40
|
+
const showColumnPanel = ref(false)
|
|
41
|
+
const columnPanelRef = ref(null)
|
|
42
|
+
|
|
43
|
+
const orderedColumns = computed(() => {
|
|
44
|
+
if (!tableRef.value) return props.columns
|
|
45
|
+
const ids = tableRef.value.table.getAllLeafColumns().map(c => c.id)
|
|
46
|
+
return ids.map(id => props.columns.find(c => c.key === id)).filter(Boolean)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
let draggedKey = null
|
|
50
|
+
const dragOverKey = ref(null)
|
|
51
|
+
|
|
52
|
+
const onDragStart = (key) => { draggedKey = key }
|
|
53
|
+
const onDragOver = (e, key) => { e.preventDefault(); dragOverKey.value = key }
|
|
54
|
+
const onDragLeave = () => { dragOverKey.value = null }
|
|
55
|
+
const onDrop = (key) => {
|
|
56
|
+
if (!draggedKey || draggedKey === key) return
|
|
57
|
+
const ids = tableRef.value?.table.getAllLeafColumns().map(c => c.id) ?? []
|
|
58
|
+
const from = ids.indexOf(draggedKey)
|
|
59
|
+
const to = ids.indexOf(key)
|
|
60
|
+
if (from < 0 || to < 0) return
|
|
61
|
+
ids.splice(from, 1)
|
|
62
|
+
ids.splice(to, 0, draggedKey)
|
|
63
|
+
tableRef.value?.setColumnOrder(ids)
|
|
64
|
+
draggedKey = null
|
|
65
|
+
dragOverKey.value = null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const onPanelOutsideClick = (e) => {
|
|
69
|
+
if (columnPanelRef.value && !columnPanelRef.value.contains(e.target)) {
|
|
70
|
+
showColumnPanel.value = false
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
watch(showColumnPanel, (v) => {
|
|
75
|
+
if (v) document.addEventListener('mousedown', onPanelOutsideClick)
|
|
76
|
+
else document.removeEventListener('mousedown', onPanelOutsideClick)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
// ─── Expose ───────────────────────────────────────────────────────────────────
|
|
41
80
|
const getSelectedRows = () => tableRef.value?.getSelectedRows()
|
|
42
81
|
const reload = () => tableRef.value?.reload()
|
|
43
82
|
const clearCache = () => tableRef.value?.clearCache()
|
|
@@ -47,103 +86,174 @@ defineExpose({ getSelectedRows, reload, clearCache, exportTable, tableRef })
|
|
|
47
86
|
</script>
|
|
48
87
|
|
|
49
88
|
<template>
|
|
50
|
-
<div class="
|
|
51
|
-
<!--
|
|
52
|
-
<div class="
|
|
53
|
-
<!--
|
|
54
|
-
<div
|
|
55
|
-
|
|
56
|
-
|
|
89
|
+
<div class="relative">
|
|
90
|
+
<!-- Card -->
|
|
91
|
+
<div class="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl overflow-hidden">
|
|
92
|
+
<!-- Toolbar -->
|
|
93
|
+
<div class="flex flex-wrap items-center gap-3 px-4 py-3 border-b border-slate-200 dark:border-slate-700">
|
|
94
|
+
<!-- Search -->
|
|
95
|
+
<div v-if="showSearch" class="relative flex-1 min-w-48">
|
|
96
|
+
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none">
|
|
97
|
+
<IconSearch class="size-4 text-slate-400" stroke="1.5" />
|
|
98
|
+
</div>
|
|
99
|
+
<input
|
|
100
|
+
v-model="search"
|
|
101
|
+
type="search"
|
|
102
|
+
:placeholder="searchPlaceholder"
|
|
103
|
+
class="block w-full rounded-lg border border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-900 text-slate-900 dark:text-white py-1.5 ps-9 pe-4 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<!-- Filter toggle -->
|
|
108
|
+
<button
|
|
109
|
+
v-if="showFilters && hasFilterableColumns"
|
|
110
|
+
type="button"
|
|
111
|
+
@click="showFilterPanel = !showFilterPanel"
|
|
112
|
+
:class="[
|
|
113
|
+
'py-1.5 px-3 inline-flex items-center gap-2 text-sm font-medium rounded-lg border transition-colors',
|
|
114
|
+
showFilterPanel || activeFilterCount > 0
|
|
115
|
+
? 'border-blue-500 bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:border-blue-500 dark:text-blue-300'
|
|
116
|
+
: 'border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700'
|
|
117
|
+
]"
|
|
118
|
+
>
|
|
119
|
+
<IconAdjustmentsHorizontal class="size-4" stroke="1.5" />
|
|
120
|
+
Filtros
|
|
121
|
+
<span
|
|
122
|
+
v-if="activeFilterCount > 0"
|
|
123
|
+
class="inline-flex items-center justify-center size-5 rounded-full bg-blue-600 text-white text-xs font-bold"
|
|
124
|
+
>{{ activeFilterCount }}</span>
|
|
125
|
+
</button>
|
|
126
|
+
|
|
127
|
+
<slot name="actions" />
|
|
128
|
+
|
|
129
|
+
<!-- Reload -->
|
|
130
|
+
<button
|
|
131
|
+
type="button"
|
|
132
|
+
@click="reload()"
|
|
133
|
+
class="py-1.5 px-2.5 inline-flex items-center gap-2 text-sm font-medium rounded-lg border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors"
|
|
134
|
+
title="Recargar"
|
|
135
|
+
>
|
|
136
|
+
<IconReload class="size-4" />
|
|
137
|
+
</button>
|
|
138
|
+
|
|
139
|
+
<!-- Cache badge -->
|
|
140
|
+
<div v-if="tableRef?.isDataFromCache && tableRef?.cached" class="group relative flex items-center">
|
|
141
|
+
<div class="flex items-center gap-x-1.5 py-1.5 px-2.5 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 rounded-lg cursor-help hover:bg-emerald-500/20 transition-colors border border-emerald-200 dark:border-emerald-800">
|
|
142
|
+
<IconBolt class="size-3.5 fill-current" />
|
|
143
|
+
<span class="text-[10px] font-bold uppercase tracking-wider">Instant</span>
|
|
144
|
+
</div>
|
|
145
|
+
<div class="absolute top-full mt-2 right-0 hidden group-hover:block w-48 p-2.5 bg-slate-900 text-white text-[11px] leading-relaxed rounded-xl shadow-2xl z-50">
|
|
146
|
+
<div class="font-bold mb-1 flex items-center gap-x-1.5 text-emerald-400">
|
|
147
|
+
<IconBolt class="size-3" /> Datos en Caché
|
|
148
|
+
</div>
|
|
149
|
+
Cargados desde memoria local. Recarga para sincronizar.
|
|
150
|
+
<div class="absolute bottom-full right-4 mb-[-1px] border-4 border-transparent border-b-slate-900"></div>
|
|
151
|
+
</div>
|
|
57
152
|
</div>
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
153
|
+
|
|
154
|
+
<!-- Column visibility toggle -->
|
|
155
|
+
<button
|
|
156
|
+
type="button"
|
|
157
|
+
@click="showColumnPanel = !showColumnPanel"
|
|
158
|
+
:class="[
|
|
159
|
+
'py-1.5 px-3 inline-flex items-center gap-2 text-sm font-medium rounded-lg border transition-colors',
|
|
160
|
+
showColumnPanel
|
|
161
|
+
? 'border-blue-500 bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:border-blue-500 dark:text-blue-300'
|
|
162
|
+
: 'border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700'
|
|
163
|
+
]"
|
|
164
|
+
>
|
|
165
|
+
<IconLayoutColumns class="size-4" />
|
|
166
|
+
Columnas
|
|
167
|
+
</button>
|
|
168
|
+
|
|
169
|
+
<!-- Export -->
|
|
170
|
+
<TableExportable
|
|
171
|
+
v-if="showExport"
|
|
172
|
+
:table-ref="tableRef"
|
|
173
|
+
:name="name"
|
|
174
|
+
:columns="columns"
|
|
63
175
|
/>
|
|
64
176
|
</div>
|
|
65
177
|
|
|
66
|
-
<!-- Filter
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
? 'border-indigo-500 bg-indigo-50 text-indigo-700 dark:bg-indigo-900/20 dark:border-indigo-500 dark:text-indigo-300'
|
|
75
|
-
: 'border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700'
|
|
76
|
-
]"
|
|
178
|
+
<!-- Filter panel -->
|
|
179
|
+
<Transition
|
|
180
|
+
enter-active-class="transition ease-out duration-150"
|
|
181
|
+
enter-from-class="opacity-0 -translate-y-2"
|
|
182
|
+
enter-to-class="opacity-100 translate-y-0"
|
|
183
|
+
leave-active-class="transition ease-in duration-100"
|
|
184
|
+
leave-from-class="opacity-100 translate-y-0"
|
|
185
|
+
leave-to-class="opacity-0 -translate-y-2"
|
|
77
186
|
>
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
</
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
type="button"
|
|
91
|
-
@click="tableRef?.toggleColumnPanel()"
|
|
92
|
-
:class="[
|
|
93
|
-
'py-1.5 px-3 inline-flex items-center gap-2 text-sm font-medium rounded-lg border transition-colors',
|
|
94
|
-
tableRef?.showColumnPanel
|
|
95
|
-
? 'border-indigo-500 bg-indigo-50 text-indigo-700 dark:bg-indigo-900/20 dark:border-indigo-500 dark:text-indigo-300'
|
|
96
|
-
: 'border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700'
|
|
97
|
-
]"
|
|
98
|
-
>
|
|
99
|
-
<IconLayoutColumns class="size-4" />
|
|
100
|
-
Columnas
|
|
101
|
-
</button>
|
|
102
|
-
|
|
103
|
-
<!-- Export -->
|
|
104
|
-
<TableExportable
|
|
105
|
-
v-if="showExport"
|
|
106
|
-
:table-ref="tableRef"
|
|
107
|
-
:name="name"
|
|
187
|
+
<div
|
|
188
|
+
v-if="showFilterPanel && hasFilterableColumns"
|
|
189
|
+
class="px-4 py-3 border-b border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-900/50"
|
|
190
|
+
>
|
|
191
|
+
<TableFilter v-model="filters" :columns="columns" />
|
|
192
|
+
</div>
|
|
193
|
+
</Transition>
|
|
194
|
+
|
|
195
|
+
<!-- Table -->
|
|
196
|
+
<Table
|
|
197
|
+
ref="tableRef"
|
|
198
|
+
:endpoint="endpoint"
|
|
108
199
|
:columns="columns"
|
|
109
|
-
|
|
200
|
+
:name="name"
|
|
201
|
+
:params="mergedParams"
|
|
202
|
+
:search="search"
|
|
203
|
+
:checkable="checkable"
|
|
204
|
+
:cached="cached"
|
|
205
|
+
:show-reload-button="showReloadButton"
|
|
206
|
+
:click-row-to-open="clickRowToOpen"
|
|
207
|
+
@row-click="emit('row-click', $event)"
|
|
208
|
+
@loaded="emit('loaded', $event)"
|
|
209
|
+
>
|
|
210
|
+
<template v-for="(_, name) in $slots" #[name]="slotProps">
|
|
211
|
+
<slot :name="name" v-bind="slotProps ?? {}" />
|
|
212
|
+
</template>
|
|
213
|
+
</Table>
|
|
110
214
|
</div>
|
|
111
215
|
|
|
112
|
-
<!--
|
|
216
|
+
<!-- Column panel — outside overflow-hidden so never clipped -->
|
|
113
217
|
<Transition
|
|
114
218
|
enter-active-class="transition ease-out duration-150"
|
|
115
|
-
enter-from-class="opacity-0
|
|
116
|
-
enter-to-class="opacity-100 translate-y-0"
|
|
219
|
+
enter-from-class="opacity-0 translate-y-1 scale-95"
|
|
220
|
+
enter-to-class="opacity-100 translate-y-0 scale-100"
|
|
117
221
|
leave-active-class="transition ease-in duration-100"
|
|
118
|
-
leave-from-class="opacity-100 translate-y-0"
|
|
119
|
-
leave-to-class="opacity-0
|
|
222
|
+
leave-from-class="opacity-100 translate-y-0 scale-100"
|
|
223
|
+
leave-to-class="opacity-0 translate-y-1 scale-95"
|
|
120
224
|
>
|
|
121
225
|
<div
|
|
122
|
-
v-if="
|
|
123
|
-
|
|
226
|
+
v-if="showColumnPanel"
|
|
227
|
+
ref="columnPanelRef"
|
|
228
|
+
class="absolute top-12 right-0 z-50 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl shadow-2xl p-3 min-w-56 max-h-80 overflow-y-auto"
|
|
124
229
|
>
|
|
125
|
-
<
|
|
230
|
+
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-2 px-1">
|
|
231
|
+
Columnas visibles
|
|
232
|
+
</p>
|
|
233
|
+
<div
|
|
234
|
+
v-for="col in orderedColumns"
|
|
235
|
+
:key="col.key"
|
|
236
|
+
draggable="true"
|
|
237
|
+
@dragstart="onDragStart(col.key)"
|
|
238
|
+
@dragover="(e) => onDragOver(e, col.key)"
|
|
239
|
+
@dragleave="onDragLeave"
|
|
240
|
+
@drop="onDrop(col.key)"
|
|
241
|
+
class="flex items-center gap-2 py-1.5 px-2 rounded-lg select-none transition-colors"
|
|
242
|
+
:class="dragOverKey === col.key
|
|
243
|
+
? 'bg-blue-50 dark:bg-blue-900/20 ring-1 ring-blue-300 dark:ring-blue-700'
|
|
244
|
+
: 'hover:bg-slate-50 dark:hover:bg-slate-700 cursor-grab'"
|
|
245
|
+
>
|
|
246
|
+
<IconGripVertical class="size-4 text-slate-300 dark:text-slate-600 shrink-0" />
|
|
247
|
+
<input
|
|
248
|
+
type="checkbox"
|
|
249
|
+
:checked="tableRef?.table.getColumn(col.key)?.getIsVisible() ?? true"
|
|
250
|
+
@change="tableRef?.table.getColumn(col.key)?.toggleVisibility()"
|
|
251
|
+
@click.stop
|
|
252
|
+
class="rounded border-gray-300 dark:bg-slate-700 dark:border-slate-600 shrink-0 cursor-pointer"
|
|
253
|
+
/>
|
|
254
|
+
<span class="text-sm text-slate-700 dark:text-slate-200 truncate">{{ col.label }}</span>
|
|
255
|
+
</div>
|
|
126
256
|
</div>
|
|
127
257
|
</Transition>
|
|
128
|
-
|
|
129
|
-
<!-- Table -->
|
|
130
|
-
<Table
|
|
131
|
-
ref="tableRef"
|
|
132
|
-
:endpoint="endpoint"
|
|
133
|
-
:columns="columns"
|
|
134
|
-
:name="name"
|
|
135
|
-
:params="mergedParams"
|
|
136
|
-
:search="search"
|
|
137
|
-
:checkable="checkable"
|
|
138
|
-
:cached="cached"
|
|
139
|
-
:show-reload-button="showReloadButton"
|
|
140
|
-
:click-row-to-open="clickRowToOpen"
|
|
141
|
-
@row-click="emit('row-click', $event)"
|
|
142
|
-
@loaded="emit('loaded', $event)"
|
|
143
|
-
>
|
|
144
|
-
<template v-for="(_, name) in $slots" #[name]="slotProps">
|
|
145
|
-
<slot :name="name" v-bind="slotProps ?? {}" />
|
|
146
|
-
</template>
|
|
147
|
-
</Table>
|
|
148
258
|
</div>
|
|
149
259
|
</template>
|
package/components/Table.vue
CHANGED
|
@@ -6,8 +6,6 @@ import {
|
|
|
6
6
|
IconSortDescendingSmallBig,
|
|
7
7
|
IconReload,
|
|
8
8
|
IconBolt,
|
|
9
|
-
IconLayoutColumns,
|
|
10
|
-
IconGripVertical,
|
|
11
9
|
} from '@tabler/icons-vue'
|
|
12
10
|
|
|
13
11
|
const props = defineProps({
|
|
@@ -285,47 +283,7 @@ onBeforeUnmount(() => {
|
|
|
285
283
|
})
|
|
286
284
|
|
|
287
285
|
// ─── Column settings panel ────────────────────────────────────────────────────
|
|
288
|
-
const
|
|
289
|
-
const columnPanelRef = ref(null)
|
|
290
|
-
|
|
291
|
-
const orderedColumns = computed(() => {
|
|
292
|
-
if (!columnOrder.value.length) return props.columns
|
|
293
|
-
return [...props.columns].sort((a, b) => {
|
|
294
|
-
const ia = columnOrder.value.indexOf(a.key)
|
|
295
|
-
const ib = columnOrder.value.indexOf(b.key)
|
|
296
|
-
return (ia < 0 ? 999 : ia) - (ib < 0 ? 999 : ib)
|
|
297
|
-
})
|
|
298
|
-
})
|
|
299
|
-
|
|
300
|
-
let draggedPanelKey = null
|
|
301
|
-
const dragOverPanelKey = ref(null)
|
|
302
|
-
|
|
303
|
-
const onPanelDragStart = (key) => { draggedPanelKey = key }
|
|
304
|
-
const onPanelDragOver = (e, key) => { e.preventDefault(); dragOverPanelKey.value = key }
|
|
305
|
-
const onPanelDragLeave = () => { dragOverPanelKey.value = null }
|
|
306
|
-
const onPanelDrop = (key) => {
|
|
307
|
-
if (!draggedPanelKey || draggedPanelKey === key) return
|
|
308
|
-
const order = [...columnOrder.value]
|
|
309
|
-
const from = order.indexOf(draggedPanelKey)
|
|
310
|
-
const to = order.indexOf(key)
|
|
311
|
-
if (from < 0 || to < 0) return
|
|
312
|
-
order.splice(from, 1)
|
|
313
|
-
order.splice(to, 0, draggedPanelKey)
|
|
314
|
-
columnOrder.value = order
|
|
315
|
-
draggedPanelKey = null
|
|
316
|
-
dragOverPanelKey.value = null
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const handlePanelOutsideClick = (e) => {
|
|
320
|
-
if (columnPanelRef.value && !columnPanelRef.value.contains(e.target)) {
|
|
321
|
-
showColumnPanel.value = false
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
watch(showColumnPanel, (v) => {
|
|
326
|
-
if (v) document.addEventListener('mousedown', handlePanelOutsideClick)
|
|
327
|
-
else document.removeEventListener('mousedown', handlePanelOutsideClick)
|
|
328
|
-
})
|
|
286
|
+
const setColumnOrder = (order) => { columnOrder.value = order }
|
|
329
287
|
|
|
330
288
|
// ─── Header drag reorder ──────────────────────────────────────────────────────
|
|
331
289
|
let draggedHeaderId = null
|
|
@@ -446,8 +404,6 @@ const reloadTable = () => {
|
|
|
446
404
|
fetchData()
|
|
447
405
|
}
|
|
448
406
|
|
|
449
|
-
const toggleColumnPanel = () => { showColumnPanel.value = !showColumnPanel.value }
|
|
450
|
-
|
|
451
407
|
defineExpose({
|
|
452
408
|
getSelectedRows,
|
|
453
409
|
loading,
|
|
@@ -455,57 +411,15 @@ defineExpose({
|
|
|
455
411
|
reload: reloadTable,
|
|
456
412
|
clearCache,
|
|
457
413
|
table,
|
|
458
|
-
|
|
459
|
-
|
|
414
|
+
setColumnOrder,
|
|
415
|
+
isDataFromCache,
|
|
416
|
+
cached: computed(() => props.cached),
|
|
460
417
|
})
|
|
461
418
|
</script>
|
|
462
419
|
|
|
463
420
|
<template>
|
|
464
421
|
<div class="relative">
|
|
465
422
|
|
|
466
|
-
<!-- Column settings panel -->
|
|
467
|
-
<Transition
|
|
468
|
-
enter-active-class="transition ease-out duration-150"
|
|
469
|
-
enter-from-class="opacity-0 translate-y-1 scale-95"
|
|
470
|
-
enter-to-class="opacity-100 translate-y-0 scale-100"
|
|
471
|
-
leave-active-class="transition ease-in duration-100"
|
|
472
|
-
leave-from-class="opacity-100 translate-y-0 scale-100"
|
|
473
|
-
leave-to-class="opacity-0 translate-y-1 scale-95"
|
|
474
|
-
>
|
|
475
|
-
<div
|
|
476
|
-
v-if="showColumnPanel"
|
|
477
|
-
ref="columnPanelRef"
|
|
478
|
-
class="absolute top-0 right-0 z-50 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl shadow-2xl p-3 min-w-56 max-h-80 overflow-y-auto"
|
|
479
|
-
>
|
|
480
|
-
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-2 px-1">
|
|
481
|
-
Columnas visibles
|
|
482
|
-
</p>
|
|
483
|
-
<div
|
|
484
|
-
v-for="col in orderedColumns"
|
|
485
|
-
:key="col.key"
|
|
486
|
-
draggable="true"
|
|
487
|
-
@dragstart="onPanelDragStart(col.key)"
|
|
488
|
-
@dragover="(e) => onPanelDragOver(e, col.key)"
|
|
489
|
-
@dragleave="onPanelDragLeave"
|
|
490
|
-
@drop="onPanelDrop(col.key)"
|
|
491
|
-
class="flex items-center gap-2 py-1.5 px-2 rounded-lg select-none transition-colors"
|
|
492
|
-
:class="dragOverPanelKey === col.key
|
|
493
|
-
? 'bg-indigo-50 dark:bg-indigo-900/20 ring-1 ring-indigo-300 dark:ring-indigo-700'
|
|
494
|
-
: 'hover:bg-slate-50 dark:hover:bg-slate-700 cursor-grab'"
|
|
495
|
-
>
|
|
496
|
-
<IconGripVertical class="size-4 text-slate-300 dark:text-slate-600 shrink-0" />
|
|
497
|
-
<input
|
|
498
|
-
type="checkbox"
|
|
499
|
-
:checked="table.getColumn(col.key)?.getIsVisible() ?? true"
|
|
500
|
-
@change="table.getColumn(col.key)?.toggleVisibility()"
|
|
501
|
-
@click.stop
|
|
502
|
-
class="rounded border-gray-300 dark:bg-slate-700 dark:border-slate-600 shrink-0 cursor-pointer"
|
|
503
|
-
/>
|
|
504
|
-
<span class="text-sm text-slate-700 dark:text-slate-200 truncate">{{ col.label }}</span>
|
|
505
|
-
</div>
|
|
506
|
-
</div>
|
|
507
|
-
</Transition>
|
|
508
|
-
|
|
509
423
|
<!-- Table view -->
|
|
510
424
|
<div v-if="!isGridView" class="overflow-x-auto relative">
|
|
511
425
|
<table
|