Files
analiticaNucleo/nuxt4-app/app/components/MetadatosCard.vue

227 lines
6.5 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>
<UCard class="brand-card border border-transparent">
<template #header>
<div class="flex flex-col gap-2">
<div class="flex items-center justify-between">
<h2 class="text-lg font-semibold brand-section-title">Tabla {{ metadata.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(recordCount) }} registros en memoria
</span>
</div>
<p v-if="metadata.description" class="text-sm text-[var(--brand-text-muted)]">
{{ metadata.description }}
</p>
</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)]">{{ metadata.primaryKey || '—' }}</dd>
</div>
<div>
<dt class="uppercase tracking-wide text-xs">Tamaño aprox.</dt>
<dd class="font-medium text-[var(--brand-text)]">{{ formatSize(metadata.approxSizeBytes) }}</dd>
</div>
<div>
<dt class="uppercase tracking-wide text-xs">Creación desde</dt>
<dd class="font-medium text-[var(--brand-text)]">{{ formatDate(metadata.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(metadata.createdAtRange?.to) }}</dd>
</div>
</dl>
<template #footer>
<div class="flex flex-col gap-3">
<div class="brand-divider pt-3 text-xs text-[var(--brand-text-muted)]">
Columnas detectadas ({{ metadata.columns?.length || 0 }}): {{ (metadata.columns || []).join(', ') || 'Ninguna' }}
</div>
<div class="flex flex-col gap-3">
<div class="flex items-center justify-between gap-3">
<div class="flex flex-col gap-1">
<span class="text-xs text-[var(--brand-text-muted)]">
{{ tableStore ? 'Última carga: ' + tableStore.formattedLastUpdated : 'No hay datos cargados' }}
</span>
<span v-if="tableStore?.isStale" class="text-xs text-yellow-400">
Los datos pueden estar desactualizados
</span>
</div>
<div class="flex gap-2">
<UButton
:loading="isLoadingLatest"
:disabled="isLoadingAll"
:ui="{ base: 'bg-[#c08040] text-[#1b1209] border border-[#d99a56] hover:bg-[#d99a56] hover:border-[#f0c07c]' }"
size="sm"
@click="loadLatestData"
>
<template #leading>
<UIcon name="i-lucide-clock" :class="{ 'animate-spin': isLoadingLatest }" />
</template>
Últimos datos
</UButton>
<UButton
:loading="isLoadingAll"
:disabled="isLoadingLatest"
:ui="{ base: 'bg-[#c08040] text-[#1b1209] border border-[#d99a56] hover:bg-[#d99a56] hover:border-[#f0c07c]' }"
size="sm"
@click="loadAllData"
>
<template #leading>
<UIcon name="i-lucide-database" :class="{ 'animate-spin': isLoadingAll }" />
</template>
Obtener todos
</UButton>
</div>
</div>
<!-- Progress Bar -->
<UProgress
v-if="isLoadingLatest || isLoadingAll"
:model-value="loadingProgress"
:max="100"
status
size="sm"
/>
</div>
</div>
</template>
</UCard>
</template>
<script setup lang="ts">
import { useTableDataStore } from '~/stores/tableDataFactory'
interface MetadataProps {
metadata: {
name: string
table: string
primaryKey?: string
rowCount?: number
approxSizeBytes?: number | null
columns?: string[]
createdAtRange?: {
from: string | null
to: string | null
}
sampleRow?: any
lastRefreshed?: string
description?: string
}
}
const props = defineProps<MetadataProps>()
const { $getTableStore } = useNuxtApp()
// Loading states
const isLoadingLatest = ref(false)
const isLoadingAll = ref(false)
const loadingProgress = ref(0)
// Get the table store for this specific datasource (using name, not table)
const tableStore = computed(() => {
if (typeof $getTableStore === 'function') {
return $getTableStore(props.metadata.name)
}
return useTableDataStore(props.metadata.name)
})
// Calculate record count from in-memory data
const recordCount = computed(() => {
return tableStore.value?.recordCount || 0
})
async function loadLatestData() {
isLoadingLatest.value = true
loadingProgress.value = 0
try {
const store = tableStore.value
if (!store) return
await store.loadLatestDataInBatches((progress) => {
loadingProgress.value = progress
})
loadingProgress.value = 100
} catch (error) {
console.error('Error loading latest data:', error)
} finally {
setTimeout(() => {
isLoadingLatest.value = false
loadingProgress.value = 0
}, 500)
}
}
async function loadAllData() {
isLoadingAll.value = true
loadingProgress.value = 0
try {
const store = tableStore.value
if (!store) return
await store.loadAllDataInBatches((progress) => {
loadingProgress.value = progress
})
loadingProgress.value = 100
} catch (error) {
console.error('Error loading all data:', error)
} finally {
setTimeout(() => {
isLoadingAll.value = false
loadingProgress.value = 0
}, 500)
}
}
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)
}
</script>