Files
analiticaNucleo/nuxt4-app/app/pages/metadatos.vue
2025-09-29 20:57:27 -06:00

219 lines
8.2 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="flex flex-col gap-8">
<UCard class="brand-card border border-transparent backdrop-blur-sm">
<template #header>
<div class="flex flex-col gap-2">
<h2 class="text-xl font-semibold text-[var(--brand-text)]">Metadatos de Tablas</h2>
<p class="text-sm text-[var(--brand-text-muted)]">
Información detallada sobre las tablas disponibles en la base de datos.
</p>
</div>
</template>
<div class="flex flex-col gap-4">
<!-- Refresh Controls -->
<div class="flex items-center justify-between">
<div class="flex flex-col gap-1">
<span class="text-sm font-medium text-[var(--brand-text)]">
Última actualización: {{ metadataStore.formattedLastUpdated }}
</span>
<span v-if="metadataStore.isStale" class="text-xs text-yellow-400">
Los metadatos pueden estar desactualizados
</span>
</div>
<UButton
:loading="metadataStore.loading"
:ui="{ base: 'bg-[#c08040] text-[#1b1209] border border-[#d99a56] hover:bg-[#d99a56] hover:border-[#f0c07c]' }"
@click="refreshMetadata"
>
<template #leading>
<UIcon name="i-lucide-refresh-cw" :class="{ 'animate-spin': metadataStore.loading }" />
</template>
Actualizar metadatos
</UButton>
</div>
<!-- Stats Summary -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="rounded-lg border border-[#3a2a16] bg-[#1c140c] px-4 py-3">
<div class="text-2xl font-bold text-[var(--brand-text)]">{{ metadataStore.totalTables }}</div>
<div class="text-xs text-[var(--brand-text-muted)] uppercase tracking-wide">Tablas totales</div>
</div>
<div class="rounded-lg border border-[#3a2a16] bg-[#1c140c] px-4 py-3">
<div class="text-2xl font-bold text-[var(--brand-text)]">{{ formatNumber(metadataStore.totalRecords) }}</div>
<div class="text-xs text-[var(--brand-text-muted)] uppercase tracking-wide">Registros totales</div>
</div>
<div class="rounded-lg border border-[#3a2a16] bg-[#1c140c] px-4 py-3">
<div class="text-2xl font-bold text-[var(--brand-text)]">{{ formatSize(totalSize) }}</div>
<div class="text-xs text-[var(--brand-text-muted)] uppercase tracking-wide">Tamaño aproximado</div>
</div>
</div>
</div>
</UCard>
<!-- Error State -->
<div v-if="metadataStore.hasError" class="rounded-lg border border-red-500/40 bg-red-500/18 p-4 text-sm text-red-200">
{{ metadataStore.error }}
</div>
<!-- Loading State -->
<UCard v-if="metadataStore.loading && !metadataStore.hasMetadata" class="brand-card border border-transparent">
<div class="flex items-center justify-center gap-3 py-10 text-[var(--brand-text-muted)]">
<span class="inline-flex h-8 w-8 animate-spin rounded-full border-2 border-[#c08040] border-t-transparent align-middle" aria-hidden="true" />
<span class="text-sm uppercase tracking-[0.3em]">Cargando metadatos...</span>
</div>
</UCard>
<!-- Metadata Cards -->
<section v-if="metadataStore.hasMetadata" class="flex flex-col gap-5">
<div v-if="metadataStore.allTables.length" class="grid gap-5 md:grid-cols-2">
<UCard v-for="meta in metadataStore.allTables" :key="meta.table" class="brand-card border border-transparent">
<template #header>
<div class="flex items-center justify-between">
<h2 class="text-lg font-semibold brand-section-title">Tabla {{ meta.table }}</h2>
<span class="brand-badge inline-flex items-center gap-1 rounded-full px-3 py-1 text-xs font-semibold tracking-wide">
{{ formatNumber(meta.rowCount) }} registros
</span>
</div>
</template>
<dl class="grid grid-cols-2 gap-3 text-sm text-[var(--brand-text-muted)]">
<div>
<dt class="uppercase tracking-wide text-xs">Clave primaria</dt>
<dd class="font-medium text-[var(--brand-text)]">{{ meta.primaryKey || '—' }}</dd>
</div>
<div>
<dt class="uppercase tracking-wide text-xs">Tamaño aprox.</dt>
<dd class="font-medium text-[var(--brand-text)]">{{ formatSize(meta.approxSizeBytes) }}</dd>
</div>
<div>
<dt class="uppercase tracking-wide text-xs">Creación desde</dt>
<dd class="font-medium text-[var(--brand-text)]">{{ formatDate(meta.createdAtRange?.from) }}</dd>
</div>
<div>
<dt class="uppercase tracking-wide text-xs">Creación hasta</dt>
<dd class="font-medium text-[var(--brand-text)]">{{ formatDate(meta.createdAtRange?.to) }}</dd>
</div>
</dl>
<template #footer>
<div class="brand-divider pt-3 text-xs text-[var(--brand-text-muted)]">
Columnas detectadas ({{ meta.columns?.length || 0 }}): {{ (meta.columns || []).join(', ') || 'Ninguna' }}
</div>
</template>
</UCard>
</div>
<!-- Sample Row Card (if available) -->
<template v-for="meta in metadataStore.allTables.filter(m => m.sampleRow)" :key="`sample-${meta.table}`">
<UCard class="brand-card border border-transparent">
<template #header>
<h2 class="text-lg font-semibold brand-section-title">Registro de ejemplo - {{ meta.table }}</h2>
</template>
<pre class="overflow-auto rounded bg-[#22180f] p-4 text-sm text-[var(--brand-text-muted)]">{{ formatSample(meta.sampleRow) }}</pre>
</UCard>
</template>
</section>
<!-- Empty State -->
<UCard v-else-if="!metadataStore.loading" class="brand-card border border-transparent">
<div class="py-10 text-center text-sm text-[var(--brand-text-muted)]">
<UIcon name="i-lucide-database-zap" class="mx-auto mb-4 size-12 text-[var(--brand-text-muted)]" />
<h3 class="text-lg font-semibold text-[var(--brand-text)] mb-2">No hay metadatos disponibles</h3>
<p class="text-sm text-[var(--brand-text-muted)] mb-4">
Haz clic en "Actualizar metadatos" para cargar la información de las tablas.
</p>
<UButton
:loading="metadataStore.loading"
:ui="{ base: 'bg-[#c08040] text-[#1b1209] border border-[#d99a56] hover:bg-[#d99a56] hover:border-[#f0c07c]' }"
@click="refreshMetadata"
>
<template #leading>
<UIcon name="i-lucide-refresh-cw" />
</template>
Cargar metadatos
</UButton>
</div>
</UCard>
</div>
</template>
<script setup lang="ts">
import { useMetadataStore } from '~/stores/metadata'
definePageMeta({
layout: 'dashboard',
title: 'Metadatos'
})
const metadataStore = useMetadataStore()
// Computed properties
const totalSize = computed(() => {
return metadataStore.allTables.reduce((sum, meta) => sum + (meta.approxSizeBytes || 0), 0)
})
// Methods
async function refreshMetadata() {
await metadataStore.refreshMetadata()
}
function formatSize(bytes: number | null | undefined): string {
if (!bytes) {
return 'No disponible'
}
if (bytes < 1024) {
return `${bytes} B`
}
const units = ['KB', 'MB', 'GB', 'TB']
let size = bytes / 1024
let unitIndex = 0
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024
unitIndex += 1
}
return `${size.toFixed(1)} ${units[unitIndex]}`
}
function formatDate(value: string | null | undefined): string {
if (!value) {
return '—'
}
const date = new Date(value)
if (Number.isNaN(date.getTime())) {
return value
}
return date.toLocaleString('es-ES', {
year: 'numeric',
month: 'short',
day: 'numeric'
})
}
function formatNumber(value: number): string {
return new Intl.NumberFormat('es-ES').format(value)
}
function formatSample(value: unknown): string {
try {
return JSON.stringify(value, null, 2)
} catch (error) {
return String(value)
}
}
// Initialize store on component mount
onMounted(async () => {
await metadataStore.initialize()
})
</script>