@innertia-solutions/nuxt-theme-spark 0.1.114 → 0.1.116
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/TableExportable.vue +127 -162
- package/package.json +1 -1
|
@@ -1,205 +1,170 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import {
|
|
3
3
|
IconFileTypeXls,
|
|
4
|
-
IconCodeDots,
|
|
5
|
-
IconFileTypePdf,
|
|
6
4
|
IconFileTypeCsv,
|
|
5
|
+
IconFileTypePdf,
|
|
6
|
+
IconCodeDots,
|
|
7
7
|
IconDownload,
|
|
8
8
|
} from '@tabler/icons-vue'
|
|
9
9
|
|
|
10
|
-
// Modal with format selector, pre-filled filename, columns checkboxes
|
|
11
10
|
const props = defineProps({
|
|
12
11
|
tableRef: { type: Object, default: null },
|
|
13
|
-
name:
|
|
14
|
-
columns:
|
|
12
|
+
name: { type: String, default: 'export' },
|
|
13
|
+
columns: { type: Array, default: () => [] },
|
|
15
14
|
})
|
|
16
15
|
|
|
17
|
-
const isOpen
|
|
18
|
-
const format
|
|
19
|
-
const filename
|
|
16
|
+
const isOpen = ref(false)
|
|
17
|
+
const format = ref('xlsx')
|
|
18
|
+
const filename = ref(props.name)
|
|
20
19
|
const selectedColumns = ref([])
|
|
21
20
|
|
|
22
|
-
watch(() => props.columns, (cols) => {
|
|
23
|
-
selectedColumns.value = cols.map(c => c.key)
|
|
24
|
-
}, { immediate: true })
|
|
25
|
-
|
|
21
|
+
watch(() => props.columns, (cols) => { selectedColumns.value = cols.map(c => c.key) }, { immediate: true })
|
|
26
22
|
watch(() => props.name, (v) => { filename.value = v })
|
|
27
23
|
|
|
28
|
-
const formats = [
|
|
29
|
-
{ value: 'xlsx', label: 'Excel', icon: 'xlsx' },
|
|
30
|
-
{ value: 'csv', label: 'CSV', icon: 'csv' },
|
|
31
|
-
{ value: 'pdf', label: 'PDF', icon: 'pdf' },
|
|
32
|
-
{ value: 'json', label: 'JSON', icon: 'json' },
|
|
33
|
-
]
|
|
34
|
-
|
|
35
24
|
const toggleColumn = (key) => {
|
|
36
25
|
const idx = selectedColumns.value.indexOf(key)
|
|
37
26
|
if (idx >= 0) selectedColumns.value.splice(idx, 1)
|
|
38
27
|
else selectedColumns.value.push(key)
|
|
39
28
|
}
|
|
40
|
-
|
|
41
|
-
const toggleAll
|
|
42
|
-
|
|
43
|
-
selectedColumns.value = []
|
|
44
|
-
else
|
|
45
|
-
selectedColumns.value = props.columns.map(c => c.key)
|
|
29
|
+
const allSelected = computed(() => selectedColumns.value.length === props.columns.length)
|
|
30
|
+
const toggleAll = () => {
|
|
31
|
+
selectedColumns.value = allSelected.value ? [] : props.columns.map(c => c.key)
|
|
46
32
|
}
|
|
47
33
|
|
|
48
|
-
const
|
|
49
|
-
|
|
34
|
+
const formats = [
|
|
35
|
+
{ value: 'xlsx', label: 'Excel' },
|
|
36
|
+
{ value: 'csv', label: 'CSV' },
|
|
37
|
+
{ value: 'pdf', label: 'PDF' },
|
|
38
|
+
{ value: 'json', label: 'JSON' },
|
|
39
|
+
]
|
|
50
40
|
|
|
51
41
|
const doExport = () => {
|
|
52
|
-
|
|
53
|
-
props.tableRef.exportTable(format.value, true, true)
|
|
54
|
-
}
|
|
42
|
+
props.tableRef?.exportTable(format.value, true, true, selectedColumns.value)
|
|
55
43
|
isOpen.value = false
|
|
56
44
|
}
|
|
57
45
|
|
|
58
|
-
const
|
|
46
|
+
const panelRef = ref(null)
|
|
47
|
+
const triggerRef = ref(null)
|
|
59
48
|
|
|
60
|
-
|
|
49
|
+
const onOutsideClick = (e) => {
|
|
50
|
+
if (
|
|
51
|
+
panelRef.value && !panelRef.value.contains(e.target) &&
|
|
52
|
+
triggerRef.value && !triggerRef.value.contains(e.target)
|
|
53
|
+
) {
|
|
54
|
+
isOpen.value = false
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
watch(isOpen, (v) => {
|
|
59
|
+
if (v) document.addEventListener('mousedown', onOutsideClick)
|
|
60
|
+
else document.removeEventListener('mousedown', onOutsideClick)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
defineExpose({ open: () => { isOpen.value = true } })
|
|
61
64
|
</script>
|
|
62
65
|
|
|
63
66
|
<template>
|
|
64
|
-
<div>
|
|
67
|
+
<div class="relative">
|
|
65
68
|
<button
|
|
69
|
+
ref="triggerRef"
|
|
66
70
|
type="button"
|
|
67
|
-
@click="isOpen =
|
|
68
|
-
class="
|
|
71
|
+
@click="isOpen = !isOpen"
|
|
72
|
+
:class="[
|
|
73
|
+
'py-1.5 px-3 inline-flex items-center gap-2 text-sm font-medium rounded-lg border transition-colors',
|
|
74
|
+
isOpen
|
|
75
|
+
? 'border-blue-500 bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:border-blue-500 dark:text-blue-300'
|
|
76
|
+
: 'border-card-line bg-card text-muted-foreground-1 hover:bg-muted-hover'
|
|
77
|
+
]"
|
|
69
78
|
>
|
|
70
|
-
<IconDownload class="
|
|
79
|
+
<IconDownload class="size-4" stroke="1.5" />
|
|
71
80
|
Exportar
|
|
72
81
|
</button>
|
|
73
82
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
<Transition
|
|
84
|
+
enter-active-class="transition ease-out duration-150"
|
|
85
|
+
enter-from-class="opacity-0 translate-y-1 scale-95"
|
|
86
|
+
enter-to-class="opacity-100 translate-y-0 scale-100"
|
|
87
|
+
leave-active-class="transition ease-in duration-100"
|
|
88
|
+
leave-from-class="opacity-100 translate-y-0 scale-100"
|
|
89
|
+
leave-to-class="opacity-0 translate-y-1 scale-95"
|
|
90
|
+
>
|
|
91
|
+
<div
|
|
92
|
+
v-if="isOpen"
|
|
93
|
+
ref="panelRef"
|
|
94
|
+
class="absolute top-full right-0 z-50 mt-1.5 bg-dropdown border border-dropdown-line rounded-xl shadow-2xl p-3 w-64"
|
|
83
95
|
>
|
|
84
|
-
<
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
96
|
+
<p class="text-[10px] font-bold text-muted-foreground uppercase tracking-widest mb-3 px-1">Exportar</p>
|
|
97
|
+
|
|
98
|
+
<!-- Format -->
|
|
99
|
+
<div class="grid grid-cols-4 gap-1.5 mb-3">
|
|
100
|
+
<button
|
|
101
|
+
v-for="f in formats"
|
|
102
|
+
:key="f.value"
|
|
103
|
+
type="button"
|
|
104
|
+
@click="format = f.value"
|
|
105
|
+
:class="[
|
|
106
|
+
'flex flex-col items-center gap-1 py-2 rounded-lg border text-xs font-medium transition-colors',
|
|
107
|
+
format === f.value
|
|
108
|
+
? 'border-blue-500 bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:border-blue-500 dark:text-blue-300'
|
|
109
|
+
: 'border-card-line text-muted-foreground-1 hover:bg-muted-hover'
|
|
110
|
+
]"
|
|
93
111
|
>
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
112
|
+
<IconFileTypeXls v-if="f.value === 'xlsx'" class="size-4" stroke="1.5" />
|
|
113
|
+
<IconFileTypeCsv v-else-if="f.value === 'csv'" class="size-4" stroke="1.5" />
|
|
114
|
+
<IconFileTypePdf v-else-if="f.value === 'pdf'" class="size-4" stroke="1.5" />
|
|
115
|
+
<IconCodeDots v-else class="size-4" stroke="1.5" />
|
|
116
|
+
{{ f.label }}
|
|
117
|
+
</button>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<!-- Filename -->
|
|
121
|
+
<div class="mb-3 px-1">
|
|
122
|
+
<label class="text-[10px] font-bold text-muted-foreground uppercase tracking-widest block mb-1.5">Archivo</label>
|
|
123
|
+
<div class="flex items-center gap-1.5">
|
|
124
|
+
<input
|
|
125
|
+
v-model="filename"
|
|
126
|
+
type="text"
|
|
127
|
+
class="flex-1 rounded-lg border border-card-line bg-card text-foreground py-1.5 px-2.5 text-xs focus:outline-none focus:ring-1 focus:ring-blue-500 min-w-0"
|
|
128
|
+
/>
|
|
129
|
+
<span class="text-xs text-muted-foreground shrink-0">.{{ format }}</span>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<!-- Columns -->
|
|
134
|
+
<div v-if="columns.length > 0" class="mb-3 px-1">
|
|
135
|
+
<div class="flex items-center justify-between mb-1.5">
|
|
136
|
+
<label class="text-[10px] font-bold text-muted-foreground uppercase tracking-widest">Columnas</label>
|
|
137
|
+
<button type="button" @click="toggleAll" class="text-[10px] text-blue-600 dark:text-blue-400 hover:underline">
|
|
138
|
+
{{ allSelected ? 'Ninguna' : 'Todas' }}
|
|
139
|
+
</button>
|
|
140
|
+
</div>
|
|
141
|
+
<div class="max-h-32 overflow-y-auto space-y-0.5">
|
|
142
|
+
<label
|
|
143
|
+
v-for="col in columns"
|
|
144
|
+
:key="col.key"
|
|
145
|
+
class="flex items-center gap-2 py-1 px-1.5 rounded-lg hover:bg-muted-hover cursor-pointer"
|
|
97
146
|
>
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
</svg>
|
|
108
|
-
</button>
|
|
109
|
-
</div>
|
|
110
|
-
|
|
111
|
-
<div class="px-6 py-5 space-y-5">
|
|
112
|
-
<!-- Format selector -->
|
|
113
|
-
<div>
|
|
114
|
-
<p class="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2">Formato</p>
|
|
115
|
-
<div class="grid grid-cols-4 gap-2">
|
|
116
|
-
<button
|
|
117
|
-
v-for="f in formats"
|
|
118
|
-
:key="f.value"
|
|
119
|
-
type="button"
|
|
120
|
-
@click="format = f.value"
|
|
121
|
-
:class="[
|
|
122
|
-
'flex flex-col items-center gap-1.5 p-3 rounded-xl border text-sm font-medium transition-colors',
|
|
123
|
-
format === f.value
|
|
124
|
-
? 'border-indigo-500 bg-indigo-50 text-indigo-700 dark:bg-indigo-900/30 dark:border-indigo-500 dark:text-indigo-300'
|
|
125
|
-
: 'border-card-line text-muted-foreground-1 hover:bg-muted-hover'
|
|
126
|
-
]"
|
|
127
|
-
>
|
|
128
|
-
<IconFileTypeXls v-if="f.value === 'xlsx'" class="size-5" stroke="1.5" />
|
|
129
|
-
<IconFileTypeCsv v-else-if="f.value === 'csv'" class="size-5" stroke="1.5" />
|
|
130
|
-
<IconFileTypePdf v-else-if="f.value === 'pdf'" class="size-5" stroke="1.5" />
|
|
131
|
-
<IconCodeDots v-else class="size-5" stroke="1.5" />
|
|
132
|
-
{{ f.label }}
|
|
133
|
-
</button>
|
|
134
|
-
</div>
|
|
135
|
-
</div>
|
|
136
|
-
|
|
137
|
-
<!-- Filename -->
|
|
138
|
-
<div>
|
|
139
|
-
<label class="text-xs font-semibold text-muted-foreground uppercase tracking-wider block mb-2">
|
|
140
|
-
Nombre de archivo
|
|
141
|
-
</label>
|
|
142
|
-
<div class="flex items-center gap-2">
|
|
143
|
-
<input
|
|
144
|
-
v-model="filename"
|
|
145
|
-
type="text"
|
|
146
|
-
class="flex-1 rounded-lg border border-card-line bg-card text-foreground py-2 px-3 text-sm focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
|
147
|
-
/>
|
|
148
|
-
<span class="text-sm text-muted-foreground">.{{ format }}</span>
|
|
149
|
-
</div>
|
|
150
|
-
</div>
|
|
151
|
-
|
|
152
|
-
<!-- Columns -->
|
|
153
|
-
<div v-if="columns.length > 0">
|
|
154
|
-
<div class="flex items-center justify-between mb-2">
|
|
155
|
-
<p class="text-xs font-semibold text-muted-foreground uppercase tracking-wider">Columnas</p>
|
|
156
|
-
<button
|
|
157
|
-
type="button"
|
|
158
|
-
@click="toggleAll"
|
|
159
|
-
class="text-xs text-indigo-600 dark:text-indigo-400 hover:underline"
|
|
160
|
-
>
|
|
161
|
-
{{ allSelected ? 'Deseleccionar todas' : 'Seleccionar todas' }}
|
|
162
|
-
</button>
|
|
163
|
-
</div>
|
|
164
|
-
<div class="grid grid-cols-2 gap-1.5 max-h-40 overflow-y-auto pr-1">
|
|
165
|
-
<label
|
|
166
|
-
v-for="col in columns"
|
|
167
|
-
:key="col.key"
|
|
168
|
-
class="flex items-center gap-2 py-1.5 px-2 rounded-lg hover:bg-muted-hover cursor-pointer"
|
|
169
|
-
>
|
|
170
|
-
<input
|
|
171
|
-
type="checkbox"
|
|
172
|
-
:checked="selectedColumns.includes(col.key)"
|
|
173
|
-
@change="toggleColumn(col.key)"
|
|
174
|
-
class="rounded border-card-line dark:bg-surface text-indigo-600"
|
|
175
|
-
/>
|
|
176
|
-
<span class="text-sm text-foreground truncate">{{ col.label }}</span>
|
|
177
|
-
</label>
|
|
178
|
-
</div>
|
|
179
|
-
</div>
|
|
180
|
-
</div>
|
|
181
|
-
|
|
182
|
-
<div class="flex justify-end gap-2 px-6 py-4 border-t border-card-line">
|
|
183
|
-
<button
|
|
184
|
-
type="button"
|
|
185
|
-
@click="isOpen = false"
|
|
186
|
-
class="py-2 px-4 text-sm font-medium rounded-lg border border-card-line text-muted-foreground-1 hover:bg-muted-hover transition-colors"
|
|
187
|
-
>
|
|
188
|
-
Cancelar
|
|
189
|
-
</button>
|
|
190
|
-
<button
|
|
191
|
-
type="button"
|
|
192
|
-
@click="doExport"
|
|
193
|
-
class="py-2 px-4 text-sm font-medium rounded-lg bg-indigo-600 text-white hover:bg-indigo-700 transition-colors inline-flex items-center gap-2"
|
|
194
|
-
>
|
|
195
|
-
<IconDownload class="size-4" stroke="1.5" />
|
|
196
|
-
Exportar
|
|
197
|
-
</button>
|
|
198
|
-
</div>
|
|
199
|
-
</div>
|
|
200
|
-
</Transition>
|
|
147
|
+
<input
|
|
148
|
+
type="checkbox"
|
|
149
|
+
:checked="selectedColumns.includes(col.key)"
|
|
150
|
+
@change="toggleColumn(col.key)"
|
|
151
|
+
class="rounded border-card-line bg-surface shrink-0 cursor-pointer"
|
|
152
|
+
/>
|
|
153
|
+
<span class="text-xs text-foreground truncate">{{ col.label }}</span>
|
|
154
|
+
</label>
|
|
155
|
+
</div>
|
|
201
156
|
</div>
|
|
202
|
-
|
|
203
|
-
|
|
157
|
+
|
|
158
|
+
<!-- Export button -->
|
|
159
|
+
<button
|
|
160
|
+
type="button"
|
|
161
|
+
@click="doExport"
|
|
162
|
+
class="w-full py-1.5 px-3 rounded-lg bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium transition-colors inline-flex items-center justify-center gap-2"
|
|
163
|
+
>
|
|
164
|
+
<IconDownload class="size-4" stroke="1.5" />
|
|
165
|
+
Exportar
|
|
166
|
+
</button>
|
|
167
|
+
</div>
|
|
168
|
+
</Transition>
|
|
204
169
|
</div>
|
|
205
170
|
</template>
|