Refactor: Migrar UI completa a Tailwind CSS v4 + shadcn-vue
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 58s
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 58s
- Reemplazar CSS nativo con Tailwind CSS v4 y utilidades custom - Crear librería de componentes UI basada en shadcn-vue (Radix Vue) - Componentes UI: Button, Card, Input, Textarea, Badge, Dialog, Avatar, DropdownMenu - Migrar todos los componentes existentes a Tailwind utilities - Convertir EventCard.js (htm) a EventCard.vue (SFC) - Implementar sistema de temas dark/light con clase .dark - Mantener efectos glassmorphism via @utility custom - Eliminar styles.css legacy
This commit is contained in:
@@ -1,59 +1,9 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="row" style="gap:6px; margin-bottom:10px; flex-wrap:wrap;">
|
||||
<button v-for="t in tables" :key="t" class="icon-btn" :class="{ active: t===active }" @click="select(t)">{{ t }}</button>
|
||||
</div>
|
||||
|
||||
<div class="row" style="gap:8px; margin-bottom:8px; flex-wrap:wrap;">
|
||||
<input v-model="q" class="toggle" placeholder="Buscar en página (texto)" style="flex:1; min-width:240px;" />
|
||||
<label class="row toggle" style="gap:6px;">
|
||||
Tamaño de página
|
||||
<select v-model.number="limit" @change="reload()" style="background:transparent; border:none; color:inherit;">
|
||||
<option :value="50">50</option>
|
||||
<option :value="100">100</option>
|
||||
<option :value="250">250</option>
|
||||
<option :value="500">500</option>
|
||||
</select>
|
||||
</label>
|
||||
<span class="chip">{{ offset + 1 }}–{{ Math.min(offset + limit, total) }} / {{ total }}</span>
|
||||
<div class="row" style="gap:6px;">
|
||||
<button class="icon-btn" :disabled="offset<=0" @click="prev">Anterior</button>
|
||||
<button class="icon-btn" :disabled="offset+limit>=total" @click="next">Siguiente</button>
|
||||
</div>
|
||||
<button class="icon-btn" @click="exportCsv">Exportar CSV</button>
|
||||
<button class="icon-btn" @click="$emit('toggle-fullscreen')">Fullscreen</button>
|
||||
</div>
|
||||
|
||||
<div class="panel" style="max-height: 60vh;">
|
||||
<div class="scroll">
|
||||
<div v-if="loading" class="muted">Cargando…</div>
|
||||
<div v-else>
|
||||
<table v-if="columns.length" style="width:100%; border-collapse: collapse;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="c in columns" :key="c" @click="toggleSort(c)" style="user-select:none; cursor:pointer; text-align:left; padding:6px; border-bottom: 1px solid rgba(255,255,255,.08);">
|
||||
{{ c }}
|
||||
<span v-if="sortBy===c">{{ sortDir==='asc' ? '▲' : '▼' }}</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, idx) in sortedRows" :key="idx">
|
||||
<td v-for="c in columns" :key="c" style="padding:6px; border-bottom: 1px solid rgba(255,255,255,.06); font-size:12px;">
|
||||
<pre style="margin:0; white-space: pre-wrap;">{{ fmt(row[c]) }}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div v-else class="muted">Sin columnas</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref, computed } from 'vue';
|
||||
import { Button, Badge, Input, Card } from '@/components/ui';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const emit = defineEmits(['toggle-fullscreen']);
|
||||
|
||||
const tables = ref([]);
|
||||
const active = ref('');
|
||||
@@ -120,7 +70,6 @@ const sortedRows = computed(() => {
|
||||
arr.sort((a, b) => {
|
||||
const va = a[key];
|
||||
const vb = b[key];
|
||||
// numeric compare if both look numeric
|
||||
const na = typeof va === 'number' || (/^-?\d+(\.\d+)?$/.test(String(va)) ? Number(va) : NaN);
|
||||
const nb = typeof vb === 'number' || (/^-?\d+(\.\d+)?$/.test(String(vb)) ? Number(vb) : NaN);
|
||||
if (!Number.isNaN(na) && !Number.isNaN(nb)) return (na - nb) * dir;
|
||||
@@ -163,6 +112,85 @@ function exportCsv() {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.icon-btn.active { outline: 2px solid rgba(255,127,187,.6); }
|
||||
</style>
|
||||
<template>
|
||||
<div>
|
||||
<!-- Table selector -->
|
||||
<div class="flex flex-wrap gap-1.5 mb-3">
|
||||
<Button
|
||||
v-for="t in tables"
|
||||
:key="t"
|
||||
:variant="t === active ? 'default' : 'ghost'"
|
||||
size="sm"
|
||||
:class="t === active && 'ring-2 ring-pink-400/60'"
|
||||
@click="select(t)"
|
||||
>
|
||||
{{ t }}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- Controls -->
|
||||
<div class="flex flex-wrap items-center gap-2 mb-2">
|
||||
<Input
|
||||
v-model="q"
|
||||
placeholder="Buscar en página (texto)"
|
||||
class="flex-1 min-w-[240px]"
|
||||
/>
|
||||
<label class="flex items-center gap-2 glass glass-border rounded-md px-3 py-2">
|
||||
<span class="text-sm">Tamaño de página</span>
|
||||
<select
|
||||
v-model.number="limit"
|
||||
@change="reload()"
|
||||
class="bg-transparent border-none text-inherit text-sm cursor-pointer"
|
||||
>
|
||||
<option :value="50">50</option>
|
||||
<option :value="100">100</option>
|
||||
<option :value="250">250</option>
|
||||
<option :value="500">500</option>
|
||||
</select>
|
||||
</label>
|
||||
<Badge>{{ offset + 1 }}–{{ Math.min(offset + limit, total) }} / {{ total }}</Badge>
|
||||
<div class="flex gap-1.5">
|
||||
<Button size="sm" :disabled="offset <= 0" @click="prev">Anterior</Button>
|
||||
<Button size="sm" :disabled="offset + limit >= total" @click="next">Siguiente</Button>
|
||||
</div>
|
||||
<Button size="sm" @click="exportCsv">Exportar CSV</Button>
|
||||
<Button size="sm" variant="ghost" @click="emit('toggle-fullscreen')">Fullscreen</Button>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<Card variant="panel" class="max-h-[60vh] border-pink-200 dark:border-pink-600/50">
|
||||
<div class="overflow-auto p-3 scroll-custom">
|
||||
<div v-if="loading" class="text-muted">Cargando…</div>
|
||||
<div v-else>
|
||||
<table v-if="columns.length" class="w-full border-collapse">
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
v-for="c in columns"
|
||||
:key="c"
|
||||
@click="toggleSort(c)"
|
||||
class="select-none cursor-pointer text-left p-1.5 border-b border-white/10 text-sm font-medium hover:bg-white/5"
|
||||
>
|
||||
{{ c }}
|
||||
<span v-if="sortBy === c" class="ml-1">{{ sortDir === 'asc' ? '▲' : '▼' }}</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, idx) in sortedRows" :key="idx" class="hover:bg-white/5">
|
||||
<td
|
||||
v-for="c in columns"
|
||||
:key="c"
|
||||
class="p-1.5 border-b border-white/5 text-xs"
|
||||
>
|
||||
<pre class="m-0 whitespace-pre-wrap font-mono">{{ fmt(row[c]) }}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div v-else class="text-muted">Sin columnas</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user