nuxtUI implementado

This commit is contained in:
2025-09-29 15:05:32 -06:00
parent 7225903fc4
commit f4621a0b70
3 changed files with 52 additions and 47 deletions

View File

@@ -0,0 +1,2 @@
@import "tailwindcss";
@import "@nuxt/ui";

View File

@@ -12,63 +12,52 @@
<form class="flex flex-col gap-6" @submit.prevent="executeRequest"> <form class="flex flex-col gap-6" @submit.prevent="executeRequest">
<div class="grid gap-4 lg:grid-cols-4"> <div class="grid gap-4 lg:grid-cols-4">
<UFormGroup label="Tipo de consulta" name="type"> <UFormField label="Tipo de consulta" name="type">
<USelectMenu <USelectMenu v-model="request.type" :items="requestTypeOptions" value-key="value" />
v-model="request.type" </UFormField>
:options="requestTypeOptions"
option-attribute="label"
value-attribute="value"
/>
</UFormGroup>
<UFormGroup label="Ámbito" name="scope"> <UFormField label="Ámbito" name="scope">
<USelectMenu <USelectMenu v-model="request.scope" :items="scopeOptions" value-key="value" />
v-model="request.scope" </UFormField>
:options="scopeOptions"
option-attribute="label"
value-attribute="value"
/>
</UFormGroup>
<UFormGroup v-if="requiresTable" label="Tabla" name="table"> <UFormField v-if="requiresTable" label="Tabla" name="table">
<USelectMenu <USelectMenu
v-model="request.table" v-model="request.table"
:options="tableOptions" :items="tableOptions"
option-attribute="label" value-key="value"
value-attribute="value"
placeholder="Selecciona una tabla" placeholder="Selecciona una tabla"
/> />
</UFormGroup> </UFormField>
<UFormGroup v-if="showsLimit" label="Límite" name="limit"> <UFormField v-if="showsLimit" label="Límite" name="limit">
<UInput v-model.number="request.limit" type="number" min="1" max="500" /> <UInput v-model.number="request.limit" type="number" min="1" max="500" />
</UFormGroup> </UFormField>
<UFormGroup v-if="requiresRecordId" label="ID del registro" name="recordId"> <UFormField v-if="requiresRecordId" label="ID del registro" name="recordId">
<UInput v-model="request.recordId" placeholder="Introduce el ID exacto" /> <UInput v-model="request.recordId" placeholder="Introduce el ID exacto" />
</UFormGroup> </UFormField>
<UFormGroup v-if="showsIdFilter" label="Filtrar por ID" name="filterId"> <UFormField v-if="showsIdFilter" label="Filtrar por ID" name="filterId">
<UInput v-model="request.filterId" placeholder="Opcional" /> <UInput v-model="request.filterId" placeholder="Opcional" />
</UFormGroup> </UFormField>
<UFormGroup v-if="showsDateFilters" label="Fecha desde" name="createdFrom"> <UFormField v-if="showsDateFilters" label="Fecha desde" name="createdFrom">
<UInput v-model="request.createdFrom" type="date" /> <UInput v-model="request.createdFrom" type="date" />
</UFormGroup> </UFormField>
<UFormGroup v-if="showsDateFilters" label="Fecha hasta" name="createdTo"> <UFormField v-if="showsDateFilters" label="Fecha hasta" name="createdTo">
<UInput v-model="request.createdTo" type="date" /> <UInput v-model="request.createdTo" type="date" />
</UFormGroup> </UFormField>
</div> </div>
<div v-if="showQueryJson" class="space-y-2"> <div v-if="showQueryJson" class="space-y-2">
<UFormGroup label="Consulta avanzada (JSON)"> <UFormField label="Consulta avanzada (JSON)" name="queryJson">
<UTextarea <UTextarea
v-model="request.queryJson" v-model="request.queryJson"
:rows="5" :rows="5"
placeholder='{ "filters": [{ "field": "estado", "operator": "eq", "value": "activo" }] }' placeholder='{ "filters": [{ "field": "estado", "operator": "eq", "value": "activo" }] }'
/> />
</UFormGroup> </UFormField>
<p v-if="queryState.error" class="text-sm text-red-300">{{ queryState.error }}</p> <p v-if="queryState.error" class="text-sm text-red-300">{{ queryState.error }}</p>
<p v-else-if="queryState.encoded" class="text-xs text-slate-400"> <p v-else-if="queryState.encoded" class="text-xs text-slate-400">
Segmento codificado: <code class="rounded bg-slate-800 px-2 py-1">{{ queryState.encoded }}</code> Segmento codificado: <code class="rounded bg-slate-800 px-2 py-1">{{ queryState.encoded }}</code>
@@ -88,7 +77,7 @@
</UAlert> </UAlert>
<div class="flex justify-end gap-2"> <div class="flex justify-end gap-2">
<UButton color="gray" variant="soft" @click="resetForm" :disabled="loading"> <UButton color="neutral" variant="soft" @click="resetForm" :disabled="loading">
Limpiar Limpiar
</UButton> </UButton>
<UButton type="submit" :loading="loading"> <UButton type="submit" :loading="loading">
@@ -199,11 +188,11 @@
</UBadge> </UBadge>
</template> </template>
<template v-else-if="dataStatsCollection.length"> <template v-else-if="dataStatsCollection.length">
<UBadge v-for="item in dataStatsCollection" :key="item.table" color="gray"> <UBadge v-for="item in dataStatsCollection" :key="item.table" color="neutral">
{{ item.table }}: {{ item.count }} registros (límite {{ item.limit ?? 's/d' }}) {{ item.table }}: {{ item.count }} registros (límite {{ item.limit ?? 's/d' }})
</UBadge> </UBadge>
</template> </template>
<UBadge v-else-if="tableData.length" color="gray"> <UBadge v-else-if="tableData.length" color="neutral">
{{ tableData.length }} registros visibles {{ tableData.length }} registros visibles
</UBadge> </UBadge>
</div> </div>
@@ -211,7 +200,8 @@
</template> </template>
<div v-if="loading" class="flex items-center justify-center py-10"> <div v-if="loading" class="flex items-center justify-center py-10">
<ULoadingIndicator size="lg" /> <span class="inline-flex h-8 w-8 animate-spin rounded-full border-2 border-slate-400 border-t-transparent align-middle" aria-hidden="true" />
<span class="sr-only">Cargando</span>
</div> </div>
<div v-else-if="!hasDataResponse" class="py-10 text-center text-sm text-slate-400"> <div v-else-if="!hasDataResponse" class="py-10 text-center text-sm text-slate-400">
Ejecuta una consulta de datos para ver resultados aquí. Ejecuta una consulta de datos para ver resultados aquí.
@@ -250,6 +240,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, reactive, ref, watch } from 'vue' import { computed, onMounted, reactive, ref, watch } from 'vue'
import { useRequestFetch } from '#imports'
type RequestType = 'data' | 'metadata' type RequestType = 'data' | 'metadata'
type MetadataScope = 'all' | 'table' | 'record' type MetadataScope = 'all' | 'table' | 'record'
@@ -279,6 +270,11 @@ const dataScopeOptions: Option<DataScope>[] = [
{ label: 'Consulta avanzada', value: 'query' } { label: 'Consulta avanzada', value: 'query' }
] ]
const DEFAULT_METADATA_SCOPE: MetadataScope = 'all'
const DEFAULT_DATA_SCOPE: DataScope = 'table'
const requestFetch = useRequestFetch()
const request = reactive<{ const request = reactive<{
type: RequestType type: RequestType
scope: RequestScope scope: RequestScope
@@ -292,7 +288,7 @@ const request = reactive<{
}>( }>(
{ {
type: 'data', type: 'data',
scope: 'table', scope: DEFAULT_DATA_SCOPE,
table: '', table: '',
recordId: '', recordId: '',
filterId: '', filterId: '',
@@ -410,7 +406,7 @@ onMounted(async () => {
watch( watch(
() => request.type, () => request.type,
(type) => { (type) => {
request.scope = type === 'metadata' ? metadataScopeOptions[0].value : dataScopeOptions[0].value request.scope = type === 'metadata' ? DEFAULT_METADATA_SCOPE : DEFAULT_DATA_SCOPE
request.recordId = '' request.recordId = ''
request.filterId = '' request.filterId = ''
request.createdFrom = '' request.createdFrom = ''
@@ -429,8 +425,11 @@ watch(
() => { () => {
if (!requiresTable.value) { if (!requiresTable.value) {
request.table = '' request.table = ''
} else if (!request.table && availableMetadata.value.length > 0) { } else if (!request.table) {
request.table = availableMetadata.value[0].table const defaultTable = availableMetadata.value[0]?.table
if (defaultTable) {
request.table = defaultTable
}
} }
if (!requiresRecordId.value) { if (!requiresRecordId.value) {
@@ -450,11 +449,14 @@ watch(
async function loadAvailableMetadata() { async function loadAvailableMetadata() {
try { try {
const metadata = await $fetch('/api/metadata') const metadata = await requestFetch('/api/metadata')
if (Array.isArray(metadata)) { if (Array.isArray(metadata)) {
availableMetadata.value = metadata availableMetadata.value = metadata
if (!request.table && metadata.length > 0 && requiresTable.value) { if (!request.table && requiresTable.value) {
request.table = metadata[0].table const firstTable = metadata[0]?.table
if (firstTable) {
request.table = firstTable
}
} }
} }
} catch (error) { } catch (error) {
@@ -496,7 +498,7 @@ function clearResults() {
} }
function resetForm() { function resetForm() {
request.scope = request.type === 'metadata' ? metadataScopeOptions[0].value : dataScopeOptions[0].value request.scope = request.type === 'metadata' ? DEFAULT_METADATA_SCOPE : DEFAULT_DATA_SCOPE
request.table = availableMetadata.value[0]?.table ?? '' request.table = availableMetadata.value[0]?.table ?? ''
request.recordId = '' request.recordId = ''
request.filterId = '' request.filterId = ''
@@ -595,7 +597,7 @@ async function executeRequest() {
errorMessage.value = null errorMessage.value = null
try { try {
const response = await $fetch(requestConfig.url, { const response = await requestFetch(requestConfig.url, {
query: requestConfig.query query: requestConfig.query
}) })

View File

@@ -3,6 +3,7 @@ export default defineNuxtConfig({
ssr: false, ssr: false,
compatibilityDate: '2025-07-15', compatibilityDate: '2025-07-15',
devtools: { enabled: true }, devtools: { enabled: true },
css: ['~/assets/css/main.css'],
modules: ['@nuxt/image', '@nuxt/ui', '@nuxt/test-utils'], modules: ['@nuxt/image', '@nuxt/ui', '@nuxt/test-utils'],
runtimeConfig: { runtimeConfig: {
supabase: { supabase: {
@@ -11,4 +12,4 @@ export default defineNuxtConfig({
process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
} }
} }
}) })