All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 48s
Agregado @ts-expect-error para suprimir la advertencia sobre la propiedad 'icon' que no está en los tipos formales pero es una customización válida en Nuxt UI.
308 lines
9.7 KiB
Vue
308 lines
9.7 KiB
Vue
<template>
|
|
<div class="container mx-auto px-4 py-8">
|
|
<div class="space-y-6">
|
|
<!-- Header -->
|
|
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-4">
|
|
<div class="flex-1">
|
|
<h1 class="text-2xl sm:text-3xl font-bold">Metabase Debug</h1>
|
|
<p class="text-sm sm:text-base text-gray-600 dark:text-gray-400 mt-1">
|
|
Herramienta de debugging para queries de Metabase
|
|
</p>
|
|
</div>
|
|
|
|
<UButton
|
|
@click="refreshCards"
|
|
:loading="loading"
|
|
icon="i-heroicons-arrow-path"
|
|
color="primary"
|
|
variant="soft"
|
|
class="w-full sm:w-auto"
|
|
>
|
|
Actualizar
|
|
</UButton>
|
|
</div>
|
|
|
|
<!-- Stats -->
|
|
<UCard class="brand-card border border-transparent">
|
|
<template #header>
|
|
<div>
|
|
<h2 class="text-2xl font-bold brand-section-title">Estadísticas de Metabase</h2>
|
|
<p class="text-sm text-[var(--brand-text-muted)] mt-1">Vista general de queries y cards configuradas</p>
|
|
</div>
|
|
</template>
|
|
|
|
<div class="grid grid-cols-2 sm:grid-cols-2 md:grid-cols-5 gap-3 sm:gap-4">
|
|
<div class="rounded-lg border border-[#3a2a16] bg-[#1c140c] px-6 py-4">
|
|
<div class="text-xs text-[var(--brand-text-muted)] uppercase tracking-wide mb-1">Total Cards</div>
|
|
<div class="text-3xl font-bold text-[var(--brand-primary)]">{{ cards.length }}</div>
|
|
</div>
|
|
|
|
<div class="rounded-lg border border-[#3a2a16] bg-[#1c140c] px-6 py-4">
|
|
<div class="text-xs text-[var(--brand-text-muted)] uppercase tracking-wide mb-1">SQL Nativo</div>
|
|
<div class="text-3xl font-bold text-[var(--brand-primary)]">{{ nativeQueries }}</div>
|
|
</div>
|
|
|
|
<div class="rounded-lg border border-[#3a2a16] bg-[#1c140c] px-6 py-4">
|
|
<div class="text-xs text-[var(--brand-text-muted)] uppercase tracking-wide mb-1">Query Builder</div>
|
|
<div class="text-3xl font-bold text-green-400">{{ queryBuilderQueries }}</div>
|
|
</div>
|
|
|
|
<div class="rounded-lg border border-[#3a2a16] bg-[#1c140c] px-6 py-4">
|
|
<div class="text-xs text-[var(--brand-text-muted)] uppercase tracking-wide mb-1">Queries Panorama</div>
|
|
<div class="text-3xl font-bold" :class="panoramaQueries.length === 9 ? 'text-green-400' : 'text-orange-400'">{{ panoramaQueries.length }}/9</div>
|
|
</div>
|
|
|
|
<div class="rounded-lg border border-[#3a2a16] bg-[#1c140c] px-6 py-4">
|
|
<div class="text-xs text-[var(--brand-text-muted)] uppercase tracking-wide mb-1">Queries Informe</div>
|
|
<div class="text-3xl font-bold" :class="informeQueries.length === 8 ? 'text-green-400' : 'text-orange-400'">{{ informeQueries.length }}/8</div>
|
|
</div>
|
|
</div>
|
|
</UCard>
|
|
|
|
<!-- Error Display -->
|
|
<UAlert
|
|
v-if="error"
|
|
color="error"
|
|
variant="soft"
|
|
:title="error"
|
|
:close-button="{ icon: 'i-heroicons-x-mark-20-solid', color: 'error', variant: 'link' }"
|
|
@close="error = null"
|
|
/>
|
|
|
|
<!-- Tabs -->
|
|
<UTabs
|
|
v-model="selectedTab"
|
|
:items="tabs"
|
|
:ui="{
|
|
root: 'space-y-4',
|
|
list: 'bg-[#1c140c]',
|
|
indicator: 'bg-[#c08040] z-10',
|
|
trigger: 'relative data-[state=active]:text-[#1b1209] data-[state=active]:hover:text-black data-[state=inactive]:text-[var(--brand-text-muted)] data-[state=inactive]:bg-transparent data-[state=inactive]:hover:text-[var(--brand-text)] data-[state=inactive]:hover:bg-[#2a1f14]/50 focus-visible:ring-2 focus-visible:ring-[#c08040] transition-colors duration-200',
|
|
label: 'font-medium text-sm relative z-20',
|
|
// @ts-expect-error - icon is a valid customization prop in Nuxt UI
|
|
icon: 'relative z-20',
|
|
content: 'py-4'
|
|
}"
|
|
>
|
|
<!-- Table View -->
|
|
<template #table>
|
|
<div class="py-4">
|
|
<MetabaseCardsTable
|
|
:cards="cards"
|
|
:loading="loading"
|
|
@select="selectCard"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Cards View -->
|
|
<template #cards>
|
|
<div class="py-4">
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
<MetabaseCardDisplay
|
|
v-for="card in cards"
|
|
:key="card.id"
|
|
:card="card"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Panorama Queries -->
|
|
<template #panorama>
|
|
<div class="py-4 space-y-4">
|
|
<UAlert
|
|
color="info"
|
|
variant="soft"
|
|
title="Queries del Panorama Facturador"
|
|
description="Estas son las 9 queries documentadas en METABASE_QUERIES_PANORAMA.md y definidas en server/config/metabase-queries.ts"
|
|
/>
|
|
|
|
<div class="grid grid-cols-1 gap-4">
|
|
<MetabaseCardDisplay
|
|
v-for="card in panoramaQueries"
|
|
:key="card.id"
|
|
:card="card"
|
|
/>
|
|
</div>
|
|
|
|
<UAlert
|
|
v-if="panoramaQueries.length < 9"
|
|
color="warning"
|
|
variant="soft"
|
|
:title="`Faltan ${9 - panoramaQueries.length} queries por encontrar`"
|
|
:description="`Queries faltantes: ${missingPanoramaQueries.join(', ')}`"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Informe Queries -->
|
|
<template #informe>
|
|
<div class="py-4 space-y-4">
|
|
<UAlert
|
|
color="info"
|
|
variant="soft"
|
|
title="Queries del Informe de Ingresos"
|
|
description="Estas son las 8 queries documentadas en METABASE_QUERIES_INFORME_INGRESOS.md y definidas en server/config/metabase-queries.ts"
|
|
/>
|
|
|
|
<div class="grid grid-cols-1 gap-4">
|
|
<MetabaseCardDisplay
|
|
v-for="card in informeQueries"
|
|
:key="card.id"
|
|
:card="card"
|
|
/>
|
|
</div>
|
|
|
|
<UAlert
|
|
v-if="informeQueries.length < 8"
|
|
color="warning"
|
|
variant="soft"
|
|
:title="`Faltan ${8 - informeQueries.length} queries por encontrar`"
|
|
:description="`Queries faltantes: ${missingInformeQueries.join(', ')}`"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Selected Card Detail -->
|
|
<template #detail>
|
|
<div v-if="selectedCard" class="py-4">
|
|
<MetabaseCardDisplay :card="selectedCard" />
|
|
</div>
|
|
</template>
|
|
</UTabs>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
definePageMeta({
|
|
layout: 'informe',
|
|
title: 'Metabase Debug'
|
|
})
|
|
|
|
const loading = ref(false)
|
|
const error = ref<string | null>(null)
|
|
const cards = ref<any[]>([])
|
|
const selectedCard = ref<any>(null)
|
|
const selectedTab = ref(0)
|
|
|
|
// Get expected query names from centralized config (via API)
|
|
const expectedPanoramaNames = ref<string[]>([])
|
|
const expectedInformeNames = ref<string[]>([])
|
|
|
|
// Fetch query config on mount
|
|
async function fetchQueryConfig() {
|
|
try {
|
|
const config = await $fetch('/api/metabase/query-config')
|
|
expectedPanoramaNames.value = config.panorama
|
|
expectedInformeNames.value = config.informe
|
|
} catch (e: any) {
|
|
console.error('Error fetching query config:', e)
|
|
}
|
|
}
|
|
|
|
const tabs = [
|
|
{
|
|
key: 'table',
|
|
label: 'Vista Tabla',
|
|
icon: 'i-heroicons-table-cells',
|
|
slot: 'table'
|
|
},
|
|
{
|
|
key: 'cards',
|
|
label: 'Vista Cards',
|
|
icon: 'i-heroicons-squares-2x2',
|
|
slot: 'cards'
|
|
},
|
|
{
|
|
key: 'panorama',
|
|
label: 'Queries Panorama',
|
|
icon: 'i-heroicons-chart-bar',
|
|
slot: 'panorama'
|
|
},
|
|
{
|
|
key: 'informe',
|
|
label: 'Queries Informe',
|
|
icon: 'i-heroicons-document-chart-bar',
|
|
slot: 'informe'
|
|
},
|
|
{
|
|
key: 'detail',
|
|
label: 'Detalle',
|
|
icon: 'i-heroicons-document-magnifying-glass',
|
|
disabled: !selectedCard.value,
|
|
slot: 'detail'
|
|
}
|
|
]
|
|
|
|
const nativeQueries = computed(() => {
|
|
return cards.value.filter(card => {
|
|
const type = card.query_type || card.dataset_query?.type
|
|
return type === 'native'
|
|
}).length
|
|
})
|
|
|
|
const queryBuilderQueries = computed(() => {
|
|
return cards.value.filter(card => {
|
|
const type = card.query_type || card.dataset_query?.type
|
|
return type === 'query'
|
|
}).length
|
|
})
|
|
|
|
// Find panorama queries using centralized config
|
|
const panoramaQueries = computed(() => {
|
|
return cards.value.filter(card =>
|
|
expectedPanoramaNames.value.includes(card.name)
|
|
)
|
|
})
|
|
|
|
// Find informe queries using centralized config
|
|
const informeQueries = computed(() => {
|
|
return cards.value.filter(card =>
|
|
expectedInformeNames.value.includes(card.name)
|
|
)
|
|
})
|
|
|
|
// Calculate missing queries
|
|
const missingPanoramaQueries = computed(() => {
|
|
const foundNames = panoramaQueries.value.map(q => q.name)
|
|
return expectedPanoramaNames.value.filter(name => !foundNames.includes(name))
|
|
})
|
|
|
|
const missingInformeQueries = computed(() => {
|
|
const foundNames = informeQueries.value.map(q => q.name)
|
|
return expectedInformeNames.value.filter(name => !foundNames.includes(name))
|
|
})
|
|
|
|
async function fetchCards() {
|
|
loading.value = true
|
|
error.value = null
|
|
|
|
try {
|
|
const result = await $fetch('/api/metabase/cards?f=all')
|
|
cards.value = Array.isArray(result) ? result : []
|
|
} catch (e: any) {
|
|
error.value = e.message || 'Error al cargar las cards de Metabase'
|
|
console.error('Error fetching cards:', e)
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
function refreshCards() {
|
|
fetchCards()
|
|
}
|
|
|
|
function selectCard(card: any) {
|
|
selectedCard.value = card
|
|
selectedTab.value = 4 // Switch to detail tab (now index 4 instead of 3)
|
|
}
|
|
|
|
// Load data on mount
|
|
onMounted(async () => {
|
|
await fetchQueryConfig()
|
|
await fetchCards()
|
|
})
|
|
</script>
|