manejo offline de los datos
This commit is contained in:
@@ -10,25 +10,52 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Table Selection Field Group -->
|
||||
<UFieldGroup>
|
||||
<UButton
|
||||
:label="selectedTable ? selectedTable.label : 'Seleccionar tabla'"
|
||||
:icon="selectedTable ? 'i-lucide-table' : 'i-lucide-loader-circle'"
|
||||
color="neutral"
|
||||
variant="subtle"
|
||||
:loading="metadataStore.loading && !metadataStore.hasMetadata"
|
||||
/>
|
||||
|
||||
<UDropdownMenu :items="tableDropdownItems" :loading="metadataStore.loading && !metadataStore.hasMetadata">
|
||||
<!-- Table Selection and Refresh Controls -->
|
||||
<div class="flex flex-col gap-4">
|
||||
<!-- Table Selector -->
|
||||
<UFieldGroup>
|
||||
<UButton
|
||||
:label="selectedTable ? selectedTable.label : 'Seleccionar tabla'"
|
||||
:icon="selectedTable ? 'i-lucide-table' : 'i-lucide-loader-circle'"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
icon="i-lucide-chevron-down"
|
||||
:disabled="metadataStore.loading && !metadataStore.hasMetadata"
|
||||
variant="subtle"
|
||||
:loading="metadataStore.loading && !metadataStore.hasMetadata"
|
||||
/>
|
||||
</UDropdownMenu>
|
||||
</UFieldGroup>
|
||||
|
||||
<UDropdownMenu :items="tableDropdownItems" :loading="metadataStore.loading && !metadataStore.hasMetadata">
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
icon="i-lucide-chevron-down"
|
||||
:disabled="metadataStore.loading && !metadataStore.hasMetadata"
|
||||
/>
|
||||
</UDropdownMenu>
|
||||
</UFieldGroup>
|
||||
|
||||
<!-- Refresh Controls (only shown when table is selected) -->
|
||||
<div v-if="selectedTable && currentTableStore" class="flex items-center justify-between p-3 rounded-lg bg-[#1c140c] border border-[#3a2a16]">
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-xs font-medium text-[var(--brand-text-muted)]">
|
||||
Última actualización: {{ currentTableStore.formattedLastUpdated }}
|
||||
</span>
|
||||
<span v-if="currentTableStore.isStale" class="text-xs text-yellow-400">
|
||||
⚠️ Los datos pueden estar desactualizados
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<UButton
|
||||
:loading="currentTableStore.isLoading"
|
||||
:ui="{ base: 'bg-[#c08040] text-[#1b1209] border border-[#d99a56] hover:bg-[#d99a56] hover:border-[#f0c07c]' }"
|
||||
size="sm"
|
||||
@click="refreshTableData"
|
||||
>
|
||||
<template #leading>
|
||||
<UIcon name="i-lucide-refresh-cw" :class="{ 'animate-spin': currentTableStore.isLoading }" />
|
||||
</template>
|
||||
Actualizar datos
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<!-- Loading State -->
|
||||
@@ -43,7 +70,7 @@
|
||||
</UCard>
|
||||
|
||||
<!-- Table Content -->
|
||||
<UCard v-else-if="selectedTable" class="brand-card border border-transparent">
|
||||
<UCard v-else-if="selectedTable && currentTableStore" class="brand-card border border-transparent">
|
||||
<template #header>
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<h2 class="text-lg font-semibold brand-section-title">
|
||||
@@ -51,20 +78,37 @@
|
||||
</h2>
|
||||
<div class="flex flex-wrap gap-2 text-xs text-[var(--brand-text-muted)]">
|
||||
<span class="brand-pill inline-flex items-center gap-2 rounded-full px-3 py-1 text-xs">
|
||||
{{ selectedTable.rowCount || 0 }} registros
|
||||
{{ currentTableStore.recordCount }} registros cargados
|
||||
</span>
|
||||
<span v-if="selectedTable.rowCount" class="brand-pill inline-flex items-center gap-2 rounded-full px-3 py-1 text-xs">
|
||||
{{ formatNumber(selectedTable.rowCount) }} total en BD
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Loading State for Table Data -->
|
||||
<div v-if="tableDataLoading" class="flex items-center justify-center gap-3 py-10 text-[var(--brand-text-muted)]">
|
||||
<div v-if="currentTableStore.isLoading && !currentTableStore.hasData" 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 datos...</span>
|
||||
</div>
|
||||
|
||||
<!-- Error State -->
|
||||
<div v-else-if="currentTableStore.hasError" class="py-10 text-center">
|
||||
<div class="rounded-lg border border-red-500/40 bg-red-500/18 p-4 text-sm text-red-200 max-w-md mx-auto">
|
||||
{{ currentTableStore.error }}
|
||||
</div>
|
||||
<UButton
|
||||
class="mt-4"
|
||||
:ui="{ base: 'bg-[#c08040] text-[#1b1209] border border-[#d99a56] hover:bg-[#d99a56] hover:border-[#f0c07c]' }"
|
||||
@click="refreshTableData"
|
||||
>
|
||||
Reintentar
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<!-- NuxtUI Table -->
|
||||
<div v-else-if="tableData.length > 0" class="flex-1 divide-y divide-accented w-full">
|
||||
<div v-else-if="currentTableStore.hasData" class="flex-1 divide-y divide-accented w-full">
|
||||
<!-- Table Controls -->
|
||||
<div class="flex items-center gap-2 px-4 py-3.5 overflow-x-auto">
|
||||
<UInput
|
||||
@@ -94,7 +138,7 @@
|
||||
<!-- Table Component -->
|
||||
<UTable
|
||||
ref="table"
|
||||
:data="tableData"
|
||||
:data="currentTableStore.allRecords"
|
||||
:columns="tableColumns"
|
||||
:global-filter="globalFilter"
|
||||
sticky
|
||||
@@ -112,7 +156,21 @@
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-else class="py-10 text-center text-sm text-[var(--brand-text-muted)]">
|
||||
No se encontraron datos en esta tabla.
|
||||
<UIcon name="i-lucide-inbox" 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 datos disponibles</h3>
|
||||
<p class="text-sm text-[var(--brand-text-muted)] mb-4">
|
||||
Haz clic en "Actualizar datos" para cargar la información de esta tabla.
|
||||
</p>
|
||||
<UButton
|
||||
:loading="currentTableStore.isLoading"
|
||||
:ui="{ base: 'bg-[#c08040] text-[#1b1209] border border-[#d99a56] hover:bg-[#d99a56] hover:border-[#f0c07c]' }"
|
||||
@click="refreshTableData"
|
||||
>
|
||||
<template #leading>
|
||||
<UIcon name="i-lucide-refresh-cw" />
|
||||
</template>
|
||||
Cargar datos
|
||||
</UButton>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
@@ -129,8 +187,8 @@
|
||||
import { h, resolveComponent } from 'vue'
|
||||
import { upperFirst } from 'scule'
|
||||
import type { TableColumn, DropdownMenuItem } from '@nuxt/ui'
|
||||
import { useRequestFetch } from '#imports'
|
||||
import { useMetadataStore } from '~/stores/metadata'
|
||||
import { useTableDataStore } from '~/stores/tableDataFactory'
|
||||
|
||||
definePageMeta({
|
||||
layout: 'dashboard',
|
||||
@@ -141,13 +199,12 @@ const UButton = resolveComponent('UButton')
|
||||
|
||||
// State
|
||||
const selectedTableName = ref<string>('')
|
||||
const tableData = ref<Record<string, unknown>[]>([])
|
||||
const tableDataLoading = ref(false)
|
||||
const globalFilter = ref('')
|
||||
const currentTableStore = ref<ReturnType<typeof useTableDataStore> | null>(null)
|
||||
|
||||
const requestFetch = useRequestFetch()
|
||||
const table = useTemplateRef('table')
|
||||
const metadataStore = useMetadataStore()
|
||||
const { $getTableStore } = useNuxtApp()
|
||||
|
||||
// Computed properties
|
||||
const selectedTable = computed(() => {
|
||||
@@ -189,9 +246,11 @@ const tableDropdownItems = computed((): DropdownMenuItem[] => {
|
||||
})
|
||||
|
||||
const tableColumns = computed((): TableColumn<Record<string, unknown>>[] => {
|
||||
if (!tableData.value.length) return []
|
||||
if (!currentTableStore.value?.hasData) return []
|
||||
|
||||
const firstRow = currentTableStore.value.allRecords[0]
|
||||
if (!firstRow) return []
|
||||
|
||||
const firstRow = tableData.value[0]
|
||||
const columns = Object.keys(firstRow)
|
||||
|
||||
return columns.map(column => ({
|
||||
@@ -236,42 +295,33 @@ const filteredRowCount = computed(() => {
|
||||
})
|
||||
|
||||
const totalRowCount = computed(() => {
|
||||
return tableData.value.length
|
||||
return currentTableStore.value?.recordCount || 0
|
||||
})
|
||||
|
||||
// Methods
|
||||
|
||||
async function selectTable(tableName: string) {
|
||||
function selectTable(tableName: string) {
|
||||
if (selectedTableName.value === tableName) return
|
||||
|
||||
selectedTableName.value = tableName
|
||||
await loadTableData(tableName)
|
||||
|
||||
// Get the table store using the plugin
|
||||
if (typeof $getTableStore === 'function') {
|
||||
const store = $getTableStore(tableName)
|
||||
if (store) {
|
||||
currentTableStore.value = store
|
||||
// Initialize the store (loads from cache or fetches)
|
||||
store.initialize()
|
||||
}
|
||||
} else {
|
||||
// Fallback: create store directly
|
||||
currentTableStore.value = useTableDataStore(tableName)
|
||||
currentTableStore.value.initialize()
|
||||
}
|
||||
}
|
||||
|
||||
async function loadTableData(tableName: string) {
|
||||
try {
|
||||
tableDataLoading.value = true
|
||||
|
||||
const response = await requestFetch(`/api/data/${tableName}`, {
|
||||
query: { limit: '100' }
|
||||
})
|
||||
|
||||
if (response && typeof response === 'object' && 'records' in response) {
|
||||
const dataset = response as {
|
||||
records?: Record<string, unknown>[]
|
||||
}
|
||||
tableData.value = Array.isArray(dataset.records) ? dataset.records : []
|
||||
} else if (Array.isArray(response)) {
|
||||
tableData.value = response
|
||||
} else {
|
||||
tableData.value = []
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading table data:', error)
|
||||
tableData.value = []
|
||||
} finally {
|
||||
tableDataLoading.value = false
|
||||
async function refreshTableData() {
|
||||
if (currentTableStore.value) {
|
||||
await currentTableStore.value.refreshData()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,6 +329,10 @@ function updateGlobalFilter(value: string) {
|
||||
globalFilter.value = value
|
||||
}
|
||||
|
||||
function formatNumber(value: number): string {
|
||||
return new Intl.NumberFormat('es-ES').format(value)
|
||||
}
|
||||
|
||||
function formatCellValue(value: unknown): string {
|
||||
if (value === null || value === undefined) {
|
||||
return '—'
|
||||
@@ -306,22 +360,6 @@ function formatCellValue(value: unknown): string {
|
||||
return stringValue
|
||||
}
|
||||
|
||||
// Watchers
|
||||
watch(selectedTableName, async (newTableName) => {
|
||||
if (newTableName) {
|
||||
await loadTableData(newTableName)
|
||||
} else {
|
||||
tableData.value = []
|
||||
}
|
||||
})
|
||||
|
||||
// Auto-select first table when metadata becomes available
|
||||
watch(() => metadataStore.hasMetadata, (hasMetadata) => {
|
||||
if (hasMetadata && !selectedTableName.value && metadataStore.allTables.length > 0) {
|
||||
selectedTableName.value = metadataStore.allTables[0].table
|
||||
}
|
||||
})
|
||||
|
||||
// Lifecycle
|
||||
onMounted(async () => {
|
||||
await metadataStore.initialize()
|
||||
@@ -330,8 +368,15 @@ onMounted(async () => {
|
||||
if (metadataStore.hasMetadata && !selectedTableName.value) {
|
||||
const firstTable = metadataStore.allTables[0]
|
||||
if (firstTable) {
|
||||
selectedTableName.value = firstTable.table
|
||||
selectTable(firstTable.table)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Auto-select first table when metadata becomes available
|
||||
watch(() => metadataStore.hasMetadata, (hasMetadata) => {
|
||||
if (hasMetadata && !selectedTableName.value && metadataStore.allTables.length > 0) {
|
||||
selectTable(metadataStore.allTables[0].table)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user