@innertia-solutions/nuxt-theme-spark 0.1.73 → 0.1.75
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 +111 -125
- package/components/Table.vue +3 -5
- package/components/TableFilter.vue +37 -44
- package/package.json +1 -1
|
@@ -24,6 +24,7 @@ const slots = useSlots()
|
|
|
24
24
|
const search = ref('')
|
|
25
25
|
const activeFilters = ref({})
|
|
26
26
|
const showFilterPanel = ref(false)
|
|
27
|
+
const filterPanelRef = ref(null)
|
|
27
28
|
const tableRef = ref(null)
|
|
28
29
|
|
|
29
30
|
// ─── Filter config ─────────────────────────────────────────────────────────────
|
|
@@ -51,10 +52,8 @@ const previewEnabled = ref(false)
|
|
|
51
52
|
const closePreview = () => { previewRow.value = null }
|
|
52
53
|
|
|
53
54
|
const handleRowClick = (row) => {
|
|
54
|
-
console.log('[Standard] handleRowClick | previewEnabled:', previewEnabled.value, '| row:', row?.name ?? row?.id)
|
|
55
55
|
if (previewEnabled.value) {
|
|
56
56
|
previewRow.value = previewRow.value?.id === row.id ? null : row
|
|
57
|
-
console.log('[Standard] previewRow =', previewRow.value?.name ?? previewRow.value?.id)
|
|
58
57
|
} else {
|
|
59
58
|
emit('row-click', row)
|
|
60
59
|
}
|
|
@@ -79,7 +78,6 @@ const startResize = (e) => {
|
|
|
79
78
|
const onEsc = (e) => { if (e.key === 'Escape' && previewRow.value) closePreview() }
|
|
80
79
|
onMounted(() => {
|
|
81
80
|
previewEnabled.value = !!slots.preview
|
|
82
|
-
console.log('[Standard] mounted | previewEnabled:', previewEnabled.value, '| slot keys:', Object.keys(slots))
|
|
83
81
|
window.addEventListener('keydown', onEsc)
|
|
84
82
|
})
|
|
85
83
|
onBeforeUnmount(() => window.removeEventListener('keydown', onEsc))
|
|
@@ -115,15 +113,24 @@ const onDrop = (key) => {
|
|
|
115
113
|
dragOverKey.value = null
|
|
116
114
|
}
|
|
117
115
|
|
|
118
|
-
const
|
|
116
|
+
const onColumnPanelOutsideClick = (e) => {
|
|
119
117
|
if (columnPanelRef.value && !columnPanelRef.value.contains(e.target)) {
|
|
120
118
|
showColumnPanel.value = false
|
|
121
119
|
}
|
|
122
120
|
}
|
|
121
|
+
const onFilterPanelOutsideClick = (e) => {
|
|
122
|
+
if (filterPanelRef.value && !filterPanelRef.value.contains(e.target)) {
|
|
123
|
+
showFilterPanel.value = false
|
|
124
|
+
}
|
|
125
|
+
}
|
|
123
126
|
|
|
124
127
|
watch(showColumnPanel, (v) => {
|
|
125
|
-
if (v) document.addEventListener('mousedown',
|
|
126
|
-
else document.removeEventListener('mousedown',
|
|
128
|
+
if (v) document.addEventListener('mousedown', onColumnPanelOutsideClick)
|
|
129
|
+
else document.removeEventListener('mousedown', onColumnPanelOutsideClick)
|
|
130
|
+
})
|
|
131
|
+
watch(showFilterPanel, (v) => {
|
|
132
|
+
if (v) document.addEventListener('mousedown', onFilterPanelOutsideClick)
|
|
133
|
+
else document.removeEventListener('mousedown', onFilterPanelOutsideClick)
|
|
127
134
|
})
|
|
128
135
|
|
|
129
136
|
// ─── Expose ───────────────────────────────────────────────────────────────────
|
|
@@ -137,140 +144,119 @@ defineExpose({ getSelectedRows, reload, clearCache, exportTable, tableRef })
|
|
|
137
144
|
|
|
138
145
|
<template>
|
|
139
146
|
<div class="relative" ref="containerRef">
|
|
140
|
-
<div :class="previewRow && previewEnabled ? 'flex items-stretch gap-3' : ''">
|
|
141
147
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
class="relative bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-2xl shadow-sm overflow-hidden"
|
|
145
|
-
:style="previewRow && previewEnabled ? { width: currentRatio + '%', minWidth: 0, flexShrink: 0 } : {}"
|
|
146
|
-
>
|
|
147
|
-
<!-- Toolbar -->
|
|
148
|
-
<div class="flex flex-wrap items-center gap-3 px-4 py-3 border-b border-slate-200 dark:border-slate-700">
|
|
149
|
-
<!-- Search -->
|
|
150
|
-
<div v-if="showSearch" class="flex-1 min-w-48">
|
|
151
|
-
<Forms.Input
|
|
152
|
-
v-model="search"
|
|
153
|
-
type="search"
|
|
154
|
-
:placeholder="searchPlaceholder"
|
|
155
|
-
:icon-left="IconSearch"
|
|
156
|
-
/>
|
|
157
|
-
</div>
|
|
148
|
+
<!-- Card único -->
|
|
149
|
+
<div class="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-2xl shadow-sm overflow-hidden">
|
|
158
150
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
@click="showFilterPanel = !showFilterPanel"
|
|
164
|
-
:class="[
|
|
165
|
-
'py-1.5 px-3 inline-flex items-center gap-2 text-sm font-medium rounded-lg border transition-colors',
|
|
166
|
-
showFilterPanel || activeFilterCount > 0
|
|
167
|
-
? 'border-blue-500 bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:border-blue-500 dark:text-blue-300'
|
|
168
|
-
: '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'
|
|
169
|
-
]"
|
|
170
|
-
>
|
|
171
|
-
<IconAdjustmentsHorizontal class="size-4" stroke="1.5" />
|
|
172
|
-
Filtros
|
|
173
|
-
<span
|
|
174
|
-
v-if="activeFilterCount > 0"
|
|
175
|
-
class="inline-flex items-center justify-center size-5 rounded-full bg-blue-600 text-white text-xs font-bold"
|
|
176
|
-
>{{ activeFilterCount }}</span>
|
|
177
|
-
</button>
|
|
178
|
-
|
|
179
|
-
<slot name="actions" />
|
|
180
|
-
|
|
181
|
-
<!-- Column visibility toggle -->
|
|
182
|
-
<button
|
|
183
|
-
type="button"
|
|
184
|
-
@click="showColumnPanel = !showColumnPanel"
|
|
185
|
-
:class="[
|
|
186
|
-
'py-1.5 px-3 inline-flex items-center gap-2 text-sm font-medium rounded-lg border transition-colors',
|
|
187
|
-
showColumnPanel
|
|
188
|
-
? 'border-blue-500 bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:border-blue-500 dark:text-blue-300'
|
|
189
|
-
: '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'
|
|
190
|
-
]"
|
|
191
|
-
>
|
|
192
|
-
<IconLayoutColumns class="size-4" />
|
|
193
|
-
Columnas
|
|
194
|
-
</button>
|
|
195
|
-
|
|
196
|
-
<!-- Export -->
|
|
197
|
-
<TableExportable
|
|
198
|
-
v-if="showExport"
|
|
199
|
-
:table-ref="tableRef"
|
|
200
|
-
:name="name"
|
|
201
|
-
:columns="columns"
|
|
202
|
-
/>
|
|
151
|
+
<!-- Toolbar -->
|
|
152
|
+
<div class="flex flex-wrap items-center gap-3 px-4 py-3 border-b border-slate-200 dark:border-slate-700">
|
|
153
|
+
<div v-if="showSearch" class="flex-1 min-w-48">
|
|
154
|
+
<Forms.Input v-model="search" type="search" :placeholder="searchPlaceholder" :icon-left="IconSearch" />
|
|
203
155
|
</div>
|
|
204
156
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
157
|
+
<button
|
|
158
|
+
v-if="showFilters && hasFilterableColumns"
|
|
159
|
+
type="button"
|
|
160
|
+
@click="showFilterPanel = !showFilterPanel"
|
|
161
|
+
:class="[
|
|
162
|
+
'py-1.5 px-3 inline-flex items-center gap-2 text-sm font-medium rounded-lg border transition-colors',
|
|
163
|
+
showFilterPanel || activeFilterCount > 0
|
|
164
|
+
? 'border-blue-500 bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:border-blue-500 dark:text-blue-300'
|
|
165
|
+
: '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'
|
|
166
|
+
]"
|
|
213
167
|
>
|
|
214
|
-
<
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
:search="search"
|
|
230
|
-
:checkable="checkable"
|
|
231
|
-
:cached="cached"
|
|
232
|
-
:show-reload-button="showReloadButton"
|
|
233
|
-
:click-row-to-open="clickRowToOpen"
|
|
234
|
-
:preview-row-id="previewRow?.id ?? null"
|
|
235
|
-
:preview-mode="!!previewEnabled"
|
|
236
|
-
@row-click="handleRowClick"
|
|
237
|
-
@loaded="emit('loaded', $event)"
|
|
168
|
+
<IconAdjustmentsHorizontal class="size-4" stroke="1.5" />
|
|
169
|
+
Filtros{{ activeFilterCount > 0 ? ` (${activeFilterCount})` : '' }}
|
|
170
|
+
</button>
|
|
171
|
+
|
|
172
|
+
<slot name="actions" />
|
|
173
|
+
|
|
174
|
+
<button
|
|
175
|
+
type="button"
|
|
176
|
+
@click="showColumnPanel = !showColumnPanel"
|
|
177
|
+
:class="[
|
|
178
|
+
'py-1.5 px-3 inline-flex items-center gap-2 text-sm font-medium rounded-lg border transition-colors',
|
|
179
|
+
showColumnPanel
|
|
180
|
+
? 'border-blue-500 bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:border-blue-500 dark:text-blue-300'
|
|
181
|
+
: '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'
|
|
182
|
+
]"
|
|
238
183
|
>
|
|
239
|
-
<
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
</Table>
|
|
243
|
-
</div>
|
|
184
|
+
<IconLayoutColumns class="size-4" />
|
|
185
|
+
Columnas
|
|
186
|
+
</button>
|
|
244
187
|
|
|
245
|
-
|
|
246
|
-
<div
|
|
247
|
-
v-if="previewRow && previewEnabled"
|
|
248
|
-
class="w-3 flex items-center justify-center cursor-col-resize shrink-0 group"
|
|
249
|
-
@mousedown="startResize"
|
|
250
|
-
>
|
|
251
|
-
<div class="w-px h-12 bg-slate-200 dark:bg-slate-600 rounded-full group-hover:bg-indigo-400 dark:group-hover:bg-indigo-500 transition-colors" />
|
|
188
|
+
<TableExportable v-if="showExport" :table-ref="tableRef" :name="name" :columns="columns" />
|
|
252
189
|
</div>
|
|
253
190
|
|
|
254
|
-
<!--
|
|
255
|
-
<
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
enter-to-class="opacity-100 translate-x-0"
|
|
259
|
-
leave-active-class="transition ease-in duration-200"
|
|
260
|
-
leave-from-class="opacity-100 translate-x-0"
|
|
261
|
-
leave-to-class="opacity-0 translate-x-4"
|
|
262
|
-
>
|
|
191
|
+
<!-- Contenido: tabla + preview en flex -->
|
|
192
|
+
<div :class="previewRow && previewEnabled ? 'flex items-stretch' : ''">
|
|
193
|
+
|
|
194
|
+
<!-- Tabla -->
|
|
263
195
|
<div
|
|
264
|
-
|
|
265
|
-
class="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-2xl shadow-sm overflow-hidden flex flex-col"
|
|
266
|
-
:style="{ width: (100 - currentRatio) + '%', minWidth: 0, flexShrink: 0 }"
|
|
196
|
+
:style="previewRow && previewEnabled ? { width: currentRatio + '%', flexShrink: 0, minWidth: 0 } : {}"
|
|
267
197
|
>
|
|
268
|
-
<
|
|
198
|
+
<Table
|
|
199
|
+
ref="tableRef"
|
|
200
|
+
:endpoint="endpoint"
|
|
201
|
+
:columns="columns"
|
|
202
|
+
:name="name"
|
|
203
|
+
:params="mergedParams"
|
|
204
|
+
:search="search"
|
|
205
|
+
:checkable="checkable"
|
|
206
|
+
:cached="cached"
|
|
207
|
+
:show-reload-button="showReloadButton"
|
|
208
|
+
:click-row-to-open="clickRowToOpen"
|
|
209
|
+
:preview-row-id="previewRow?.id ?? null"
|
|
210
|
+
:preview-mode="!!previewEnabled"
|
|
211
|
+
@row-click="handleRowClick"
|
|
212
|
+
@loaded="emit('loaded', $event)"
|
|
213
|
+
>
|
|
214
|
+
<template v-for="(_, name) in $slots" #[name]="slotProps">
|
|
215
|
+
<slot :name="name" v-bind="slotProps ?? {}" />
|
|
216
|
+
</template>
|
|
217
|
+
</Table>
|
|
269
218
|
</div>
|
|
270
|
-
</Transition>
|
|
271
219
|
|
|
220
|
+
<!-- Divider + preview -->
|
|
221
|
+
<template v-if="previewRow && previewEnabled">
|
|
222
|
+
<!-- Resize handle -->
|
|
223
|
+
<div
|
|
224
|
+
class="w-1 shrink-0 cursor-col-resize bg-slate-100 dark:bg-slate-700/60 hover:bg-indigo-300 dark:hover:bg-indigo-600 transition-colors"
|
|
225
|
+
@mousedown="startResize"
|
|
226
|
+
/>
|
|
227
|
+
<!-- Preview -->
|
|
228
|
+
<div
|
|
229
|
+
class="flex flex-col overflow-y-auto border-l border-slate-200 dark:border-slate-700"
|
|
230
|
+
:style="{ width: (100 - currentRatio) + '%', flexShrink: 0, minWidth: 0 }"
|
|
231
|
+
>
|
|
232
|
+
<slot name="preview" :row="previewRow" :close="closePreview" />
|
|
233
|
+
</div>
|
|
234
|
+
</template>
|
|
235
|
+
|
|
236
|
+
</div>
|
|
272
237
|
</div>
|
|
273
238
|
|
|
239
|
+
<!-- Filter panel — outside overflow-hidden so never clipped -->
|
|
240
|
+
<Transition
|
|
241
|
+
enter-active-class="transition ease-out duration-150"
|
|
242
|
+
enter-from-class="opacity-0 translate-y-1 scale-95"
|
|
243
|
+
enter-to-class="opacity-100 translate-y-0 scale-100"
|
|
244
|
+
leave-active-class="transition ease-in duration-100"
|
|
245
|
+
leave-from-class="opacity-100 translate-y-0 scale-100"
|
|
246
|
+
leave-to-class="opacity-0 translate-y-1 scale-95"
|
|
247
|
+
>
|
|
248
|
+
<div
|
|
249
|
+
v-if="showFilterPanel && hasFilterableColumns"
|
|
250
|
+
ref="filterPanelRef"
|
|
251
|
+
class="absolute top-12 left-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-64 max-h-96 overflow-y-auto"
|
|
252
|
+
>
|
|
253
|
+
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-3 px-1">
|
|
254
|
+
Filtros
|
|
255
|
+
</p>
|
|
256
|
+
<TableFilter v-model="activeFilters" :columns="filtersConfig" />
|
|
257
|
+
</div>
|
|
258
|
+
</Transition>
|
|
259
|
+
|
|
274
260
|
<!-- Column panel — outside overflow-hidden so never clipped -->
|
|
275
261
|
<Transition
|
|
276
262
|
enter-active-class="transition ease-out duration-150"
|
package/components/Table.vue
CHANGED
|
@@ -318,13 +318,11 @@ const measureText = (text, font) => {
|
|
|
318
318
|
|
|
319
319
|
const autoSizeColumn = (header) => {
|
|
320
320
|
const colId = header.column.id
|
|
321
|
-
const pad = 32
|
|
321
|
+
const pad = 32
|
|
322
322
|
|
|
323
|
-
// Measure header label
|
|
324
323
|
const label = header.column.columnDef.meta?.label ?? header.id
|
|
325
|
-
let max = measureText(label, '500 12px ui-sans-serif,system-ui,sans-serif') + pad + 20
|
|
324
|
+
let max = measureText(label, '500 12px ui-sans-serif,system-ui,sans-serif') + pad + 20
|
|
326
325
|
|
|
327
|
-
// Measure all visible data cells
|
|
328
326
|
if (tableBodyRef.value) {
|
|
329
327
|
tableBodyRef.value.querySelectorAll(`td[data-col-id="${colId}"]`).forEach(td => {
|
|
330
328
|
const w = measureText(td.textContent?.trim(), '14px ui-sans-serif,system-ui,sans-serif') + pad
|
|
@@ -332,7 +330,7 @@ const autoSizeColumn = (header) => {
|
|
|
332
330
|
})
|
|
333
331
|
}
|
|
334
332
|
|
|
335
|
-
|
|
333
|
+
table.setColumnSizing(prev => ({ ...prev, [colId]: Math.ceil(max) }))
|
|
336
334
|
}
|
|
337
335
|
|
|
338
336
|
const onHeaderDragStart = (colId) => { draggedHeaderId = colId }
|
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
// Reusable filter panel — reads filterType from column definitions
|
|
3
|
-
// Column definition: { key, label, filterType: 'text' | 'select' | 'daterange', filterOptions?: [{value, label}] }
|
|
4
|
-
|
|
5
2
|
const props = defineProps({
|
|
6
|
-
modelValue: { type: Object, default: () => ({}) },
|
|
7
|
-
columns:
|
|
3
|
+
modelValue: { type: Object, default: () => ({}) },
|
|
4
|
+
columns: { type: Array, required: true },
|
|
8
5
|
})
|
|
9
6
|
|
|
10
7
|
const emit = defineEmits(['update:modelValue'])
|
|
11
8
|
|
|
12
|
-
const filterableColumns = computed(() =>
|
|
13
|
-
props.columns.filter(c => c.filterType)
|
|
14
|
-
)
|
|
9
|
+
const filterableColumns = computed(() => props.columns.filter(c => c.filterType))
|
|
15
10
|
|
|
16
11
|
const localFilters = ref({ ...props.modelValue })
|
|
17
12
|
|
|
@@ -20,7 +15,7 @@ watch(() => props.modelValue, (v) => {
|
|
|
20
15
|
}, { deep: true })
|
|
21
16
|
|
|
22
17
|
const updateFilter = (key, value) => {
|
|
23
|
-
localFilters.value[key] = value
|
|
18
|
+
localFilters.value[key] = value || null
|
|
24
19
|
emit('update:modelValue', { ...localFilters.value })
|
|
25
20
|
}
|
|
26
21
|
|
|
@@ -35,66 +30,64 @@ const activeCount = computed(() =>
|
|
|
35
30
|
</script>
|
|
36
31
|
|
|
37
32
|
<template>
|
|
38
|
-
<div class="
|
|
33
|
+
<div class="space-y-3">
|
|
39
34
|
<template v-for="col in filterableColumns" :key="col.key">
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
|
|
36
|
+
<!-- text -->
|
|
37
|
+
<div v-if="col.filterType === 'text'">
|
|
38
|
+
<label class="block text-xs font-medium text-slate-500 dark:text-slate-400 mb-1">{{ col.label }}</label>
|
|
43
39
|
<input
|
|
44
40
|
type="text"
|
|
45
41
|
:value="localFilters[col.key] ?? ''"
|
|
46
42
|
@input="updateFilter(col.key, $event.target.value)"
|
|
47
43
|
:placeholder="`Filtrar ${col.label.toLowerCase()}...`"
|
|
48
|
-
class="rounded-lg border border-gray-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-900 dark:text-white py-
|
|
44
|
+
class="w-full rounded-lg border border-gray-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-900 dark:text-white py-1.5 px-3 text-sm focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
|
49
45
|
/>
|
|
50
46
|
</div>
|
|
51
47
|
|
|
52
|
-
<!-- select
|
|
53
|
-
<div v-else-if="col.filterType === 'select'"
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
:value="
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
<option value="">Todos</option>
|
|
61
|
-
<option
|
|
62
|
-
v-for="opt in col.filterOptions ?? []"
|
|
63
|
-
:key="opt.value"
|
|
64
|
-
:value="opt.value"
|
|
65
|
-
>{{ opt.label }}</option>
|
|
66
|
-
</select>
|
|
48
|
+
<!-- select -->
|
|
49
|
+
<div v-else-if="col.filterType === 'select'">
|
|
50
|
+
<Forms.Select
|
|
51
|
+
:model-value="localFilters[col.key] ?? ''"
|
|
52
|
+
@update:model-value="updateFilter(col.key, $event)"
|
|
53
|
+
:options="[{ value: '', label: 'Todos' }, ...(col.filterOptions ?? [])]"
|
|
54
|
+
:label="col.label"
|
|
55
|
+
/>
|
|
67
56
|
</div>
|
|
68
57
|
|
|
69
|
-
<!-- daterange
|
|
70
|
-
<div v-else-if="col.filterType === 'daterange'"
|
|
71
|
-
<label class="text-xs text-slate-500 dark:text-slate-400">{{ col.label }}</label>
|
|
58
|
+
<!-- daterange -->
|
|
59
|
+
<div v-else-if="col.filterType === 'daterange'">
|
|
60
|
+
<label class="block text-xs font-medium text-slate-500 dark:text-slate-400 mb-1">{{ col.label }}</label>
|
|
72
61
|
<div class="flex items-center gap-1.5">
|
|
73
62
|
<input
|
|
74
63
|
type="date"
|
|
75
64
|
:value="localFilters[col.key]?.from ?? ''"
|
|
76
65
|
@change="updateFilter(col.key, { ...localFilters[col.key], from: $event.target.value || null })"
|
|
77
|
-
class="rounded-lg border border-gray-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-900 dark:text-white py-
|
|
66
|
+
class="flex-1 rounded-lg border border-gray-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-900 dark:text-white py-1.5 px-2 text-sm focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
|
78
67
|
/>
|
|
79
|
-
<span class="text-slate-400 text-
|
|
68
|
+
<span class="text-slate-400 text-xs shrink-0">—</span>
|
|
80
69
|
<input
|
|
81
70
|
type="date"
|
|
82
71
|
:value="localFilters[col.key]?.to ?? ''"
|
|
83
72
|
@change="updateFilter(col.key, { ...localFilters[col.key], to: $event.target.value || null })"
|
|
84
|
-
class="rounded-lg border border-gray-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-900 dark:text-white py-
|
|
73
|
+
class="flex-1 rounded-lg border border-gray-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-900 dark:text-white py-1.5 px-2 text-sm focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
|
85
74
|
/>
|
|
86
75
|
</div>
|
|
87
76
|
</div>
|
|
77
|
+
|
|
88
78
|
</template>
|
|
89
79
|
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
80
|
+
<div class="pt-2 border-t border-slate-100 dark:border-slate-700/60">
|
|
81
|
+
<button
|
|
82
|
+
v-if="activeCount > 0"
|
|
83
|
+
type="button"
|
|
84
|
+
@click="clearAll"
|
|
85
|
+
class="w-full py-1.5 px-3 text-sm text-rose-600 dark:text-rose-400 hover:bg-rose-50 dark:hover:bg-rose-500/10 rounded-lg transition-colors flex items-center justify-center gap-1.5"
|
|
86
|
+
>
|
|
87
|
+
<svg class="size-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
|
88
|
+
Limpiar filtros
|
|
89
|
+
</button>
|
|
90
|
+
<p v-else class="text-xs text-center text-slate-400 dark:text-slate-500 py-0.5">Sin filtros activos</p>
|
|
91
|
+
</div>
|
|
99
92
|
</div>
|
|
100
93
|
</template>
|