nuxtUI implementado
This commit is contained in:
2
nuxt4-app/app/assets/css/main.css
Normal file
2
nuxt4-app/app/assets/css/main.css
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
@import "@nuxt/ui";
|
||||||
@@ -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
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
Reference in New Issue
Block a user