pwa configuration lista
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<UApp>
|
||||
<div class="min-h-screen bg-slate-950 text-slate-100">
|
||||
<div class="brand-shell text-[#fef9f0]">
|
||||
<NuxtRouteAnnouncer />
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
|
||||
@@ -1,2 +1,69 @@
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui";
|
||||
|
||||
:root {
|
||||
--brand-bg: #14100b;
|
||||
--brand-surface: #1f180f;
|
||||
--brand-border: #3a2a16;
|
||||
--brand-primary: #e0c080;
|
||||
--brand-primary-strong: #c08040;
|
||||
--brand-accent: #ffe0a0;
|
||||
--brand-text: #fef9f0;
|
||||
--brand-text-muted: #d8c7a6;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
background-color: var(--brand-bg);
|
||||
color: var(--brand-text);
|
||||
}
|
||||
|
||||
.brand-shell {
|
||||
background: radial-gradient(circle at 20% 20%, rgba(255, 224, 160, 0.08), transparent 55%),
|
||||
radial-gradient(circle at 80% 10%, rgba(192, 128, 64, 0.08), transparent 45%),
|
||||
var(--brand-bg);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.brand-card {
|
||||
background: linear-gradient(145deg, rgba(20, 16, 11, 0.95), rgba(31, 24, 15, 0.85));
|
||||
border: 1px solid rgba(224, 192, 128, 0.12);
|
||||
box-shadow: 0 12px 40px rgba(12, 8, 4, 0.55);
|
||||
}
|
||||
|
||||
.brand-divider {
|
||||
border-top: 1px solid rgba(224, 192, 128, 0.16);
|
||||
}
|
||||
|
||||
.brand-chip {
|
||||
background: rgba(255, 224, 160, 0.08);
|
||||
border: 1px solid rgba(255, 224, 160, 0.18);
|
||||
color: var(--brand-accent);
|
||||
}
|
||||
|
||||
.brand-table thead {
|
||||
background: linear-gradient(90deg, rgba(32, 24, 15, 0.95), rgba(24, 18, 11, 0.95));
|
||||
}
|
||||
|
||||
.brand-table tbody tr {
|
||||
transition: background 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.brand-table tbody tr:hover {
|
||||
background: rgba(255, 224, 160, 0.06);
|
||||
}
|
||||
|
||||
.brand-pill {
|
||||
background: rgba(224, 192, 128, 0.14);
|
||||
border: 1px solid rgba(224, 192, 128, 0.28);
|
||||
color: var(--brand-primary);
|
||||
}
|
||||
|
||||
.brand-badge {
|
||||
color: var(--brand-bg);
|
||||
background: var(--brand-primary);
|
||||
}
|
||||
|
||||
.brand-section-title {
|
||||
color: var(--brand-primary);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,32 @@
|
||||
<template>
|
||||
<div class="mx-auto flex max-w-6xl flex-col gap-6 px-4 py-10">
|
||||
<UCard>
|
||||
<div class="mx-auto flex max-w-7xl flex-col gap-10 px-4 py-14 text-[#fef9f0]">
|
||||
<UCard class="brand-card border border-transparent backdrop-blur-sm">
|
||||
<template #header>
|
||||
<div class="flex flex-col gap-2">
|
||||
<h1 class="text-2xl font-semibold">Constructor de consultas</h1>
|
||||
<p class="text-sm text-slate-300">
|
||||
Arma una solicitud a los endpoints del backend y visualiza los resultados en modo solo lectura.
|
||||
</p>
|
||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div class="flex items-center gap-5">
|
||||
<img
|
||||
src="/logo.png"
|
||||
alt="Analítica Nucleo"
|
||||
class="h-16 w-16 rounded-full border border-[#ffe0a0]/40 shadow-lg shadow-[#c08040]/25"
|
||||
/>
|
||||
<div class="space-y-1">
|
||||
<h1 class="text-3xl font-semibold tracking-tight text-[var(--brand-text)]">
|
||||
Analítica Nucleo Data Studio
|
||||
</h1>
|
||||
<p class="text-sm text-[var(--brand-text-muted)]">
|
||||
Explora y valida tus tablas Supabase desde un único panel en modo lectura.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="brand-pill inline-flex items-center gap-2 rounded-full px-3 py-1 text-xs uppercase tracking-[0.18em]">
|
||||
<span class="inline-block h-2 w-2 rounded-full bg-[#ffe0a0]"></span>
|
||||
Solo lectura
|
||||
</span>
|
||||
<span class="brand-pill inline-flex items-center gap-2 rounded-full px-3 py-1 text-xs uppercase tracking-[0.18em]">
|
||||
Multi-fuente
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -59,168 +79,195 @@
|
||||
/>
|
||||
</UFormField>
|
||||
<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">
|
||||
Segmento codificado: <code class="rounded bg-slate-800 px-2 py-1">{{ queryState.encoded }}</code>
|
||||
<p v-else-if="queryState.encoded" class="text-xs text-[var(--brand-text-muted)]">
|
||||
Segmento codificado:
|
||||
<code class="rounded bg-[#2a2014] px-2 py-1 text-[var(--brand-accent)]">{{ queryState.encoded }}</code>
|
||||
</p>
|
||||
<p class="text-xs text-slate-500">
|
||||
Se codifica automáticamente en base64-url para construir la ruta <code>/api/data/{{ request.table || ':tabla' }}/{{
|
||||
queryState.encoded || ':query'
|
||||
}}</code>.
|
||||
<p class="text-xs text-[var(--brand-text-muted)]">
|
||||
Se codifica automáticamente en base64-url para construir la ruta
|
||||
<code class="rounded bg-[#2a2014] px-2 py-1 text-[var(--brand-accent)]">
|
||||
/api/data/{{ request.table || ':tabla' }}/{{ queryState.encoded || ':query' }}
|
||||
</code>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<UAlert color="primary" variant="soft">
|
||||
<template #title>Solicitud generada</template>
|
||||
<template #description>
|
||||
<code class="break-all text-sm">GET {{ requestPreview }}</code>
|
||||
</template>
|
||||
</UAlert>
|
||||
<div class="flex flex-col gap-3 rounded-lg border border-[#3a2a16] bg-[#1c140c] px-4 py-3 shadow-inner shadow-black/30">
|
||||
<span class="text-xs font-semibold uppercase tracking-[0.28em] text-[var(--brand-text-muted)]">
|
||||
Solicitud generada
|
||||
</span>
|
||||
<code class="break-all text-sm text-[var(--brand-accent)]">GET {{ requestPreview }}</code>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2">
|
||||
<UButton color="neutral" variant="soft" @click="resetForm" :disabled="loading">
|
||||
<UButton
|
||||
variant="soft"
|
||||
:ui="{ base: 'bg-transparent border border-[#3a2a16] text-[var(--brand-text-muted)] hover:bg-[#2a2014] hover:border-[#c08040]/60' }"
|
||||
@click="resetForm"
|
||||
:disabled="loading"
|
||||
>
|
||||
Limpiar
|
||||
</UButton>
|
||||
<UButton type="submit" :loading="loading">
|
||||
<UButton
|
||||
type="submit"
|
||||
:loading="loading"
|
||||
:ui="{ base: 'bg-[#c08040] text-[#1b1209] border border-[#d99a56] hover:bg-[#d99a56] hover:border-[#f0c07c]' }"
|
||||
>
|
||||
Ejecutar consulta
|
||||
</UButton>
|
||||
</div>
|
||||
</form>
|
||||
</UCard>
|
||||
|
||||
<div v-if="errorMessage" class="rounded-lg border border-red-500 bg-red-500/10 p-4 text-sm text-red-200">
|
||||
<div v-if="errorMessage" class="rounded-lg border border-red-500/40 bg-red-500/18 p-4 text-sm text-red-200">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
|
||||
<section v-if="hasMetadataResponse" class="flex flex-col gap-4">
|
||||
<div v-if="metadataCollection.length" class="grid gap-4 md:grid-cols-2">
|
||||
<UCard v-for="meta in metadataCollection" :key="meta.table">
|
||||
<section v-if="hasMetadataResponse" class="flex flex-col gap-5">
|
||||
<div v-if="metadataCollection.length" class="grid gap-5 md:grid-cols-2">
|
||||
<UCard v-for="meta in metadataCollection" :key="meta.table" class="brand-card border border-transparent">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-lg font-semibold">Tabla {{ meta.table }}</h2>
|
||||
<UBadge color="primary">{{ meta.rowCount }} registros</UBadge>
|
||||
<h2 class="text-lg font-semibold brand-section-title">Tabla {{ meta.table }}</h2>
|
||||
<span class="brand-badge inline-flex items-center gap-1 rounded-full px-3 py-1 text-xs font-semibold tracking-wide">
|
||||
{{ meta.rowCount }} registros
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<dl class="grid grid-cols-2 gap-2 text-sm">
|
||||
<dl class="grid grid-cols-2 gap-3 text-sm text-[var(--brand-text-muted)]">
|
||||
<div>
|
||||
<dt class="text-slate-400">Clave primaria</dt>
|
||||
<dd class="font-medium">{{ meta.primaryKey }}</dd>
|
||||
<dt class="uppercase tracking-wide text-xs">Clave primaria</dt>
|
||||
<dd class="font-medium text-[var(--brand-text)]">{{ meta.primaryKey }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-slate-400">Tamaño aprox.</dt>
|
||||
<dd class="font-medium">{{ formatSize(meta.approxSizeBytes) }}</dd>
|
||||
<dt class="uppercase tracking-wide text-xs">Tamaño aprox.</dt>
|
||||
<dd class="font-medium text-[var(--brand-text)]">{{ formatSize(meta.approxSizeBytes) }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-slate-400">Creación desde</dt>
|
||||
<dd class="font-medium">{{ formatDate(meta.createdAtRange?.from) }}</dd>
|
||||
<dt class="uppercase tracking-wide text-xs">Creación desde</dt>
|
||||
<dd class="font-medium text-[var(--brand-text)]">{{ formatDate(meta.createdAtRange?.from) }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-slate-400">Creación hasta</dt>
|
||||
<dd class="font-medium">{{ formatDate(meta.createdAtRange?.to) }}</dd>
|
||||
<dt class="uppercase tracking-wide text-xs">Creación hasta</dt>
|
||||
<dd class="font-medium text-[var(--brand-text)]">{{ formatDate(meta.createdAtRange?.to) }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<template #footer>
|
||||
<div class="text-xs text-slate-400">
|
||||
<div class="brand-divider pt-3 text-xs text-[var(--brand-text-muted)]">
|
||||
Columnas detectadas: {{ meta.columns.join(', ') }}
|
||||
</div>
|
||||
</template>
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
<div v-else-if="activeMetadata" class="grid gap-4 md:grid-cols-2">
|
||||
<UCard>
|
||||
<div v-else-if="activeMetadata" class="grid gap-5 md:grid-cols-2">
|
||||
<UCard class="brand-card border border-transparent">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-lg font-semibold">Resumen de {{ activeMetadata.table }}</h2>
|
||||
<UBadge color="primary">{{ activeMetadata.rowCount }} registros</UBadge>
|
||||
<h2 class="text-lg font-semibold brand-section-title">Resumen de {{ activeMetadata.table }}</h2>
|
||||
<span class="brand-badge inline-flex items-center gap-1 rounded-full px-3 py-1 text-xs font-semibold tracking-wide">
|
||||
{{ activeMetadata.rowCount }} registros
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<dl class="grid grid-cols-2 gap-2 text-sm">
|
||||
<dl class="grid grid-cols-2 gap-3 text-sm text-[var(--brand-text-muted)]">
|
||||
<div>
|
||||
<dt class="text-slate-400">Clave primaria</dt>
|
||||
<dd class="font-medium">{{ activeMetadata.primaryKey }}</dd>
|
||||
<dt class="uppercase tracking-wide text-xs">Clave primaria</dt>
|
||||
<dd class="font-medium text-[var(--brand-text)]">{{ activeMetadata.primaryKey }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-slate-400">Última consulta</dt>
|
||||
<dd class="font-medium">{{ formatDate(activeMetadata.lastRefreshed) }}</dd>
|
||||
<dt class="uppercase tracking-wide text-xs">Última consulta</dt>
|
||||
<dd class="font-medium text-[var(--brand-text)]">{{ formatDate(activeMetadata.lastRefreshed) }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-slate-400">Tamaño aprox.</dt>
|
||||
<dd class="font-medium">{{ formatSize(activeMetadata.approxSizeBytes) }}</dd>
|
||||
<dt class="uppercase tracking-wide text-xs">Tamaño aprox.</dt>
|
||||
<dd class="font-medium text-[var(--brand-text)]">{{ formatSize(activeMetadata.approxSizeBytes) }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-slate-400">Rango de creación</dt>
|
||||
<dd class="font-medium">
|
||||
<dt class="uppercase tracking-wide text-xs">Rango de creación</dt>
|
||||
<dd class="font-medium text-[var(--brand-text)]">
|
||||
{{ formatDate(activeMetadata.createdAtRange?.from) }} — {{ formatDate(activeMetadata.createdAtRange?.to) }}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<template #footer>
|
||||
<div class="text-xs text-slate-400">
|
||||
<div class="brand-divider pt-3 text-xs text-[var(--brand-text-muted)]">
|
||||
Columnas detectadas: {{ activeMetadata.columns.join(', ') }}
|
||||
</div>
|
||||
</template>
|
||||
</UCard>
|
||||
|
||||
<UCard v-if="activeMetadata.sampleRow">
|
||||
<UCard v-if="activeMetadata.sampleRow" class="brand-card border border-transparent">
|
||||
<template #header>
|
||||
<h2 class="text-lg font-semibold">Registro de ejemplo</h2>
|
||||
<h2 class="text-lg font-semibold brand-section-title">Registro de ejemplo</h2>
|
||||
</template>
|
||||
<pre class="overflow-auto rounded bg-slate-900 p-4 text-sm">{{ formatSample(activeMetadata.sampleRow) }}</pre>
|
||||
<pre class="overflow-auto rounded bg-[#22180f] p-4 text-sm text-[var(--brand-text-muted)]">
|
||||
{{ formatSample(activeMetadata.sampleRow) }}
|
||||
</pre>
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
<UCard v-if="metadataRecord">
|
||||
<UCard v-if="metadataRecord" class="brand-card border border-transparent">
|
||||
<template #header>
|
||||
<h2 class="text-lg font-semibold">Metadata del registro {{ metadataRecord.id }}</h2>
|
||||
<h2 class="text-lg font-semibold brand-section-title">Metadata del registro {{ metadataRecord.id }}</h2>
|
||||
</template>
|
||||
<pre class="overflow-auto rounded bg-slate-900 p-4 text-sm">{{ formatSample(metadataRecord.metadata) }}</pre>
|
||||
<pre class="overflow-auto rounded bg-[#22180f] p-4 text-sm text-[var(--brand-text-muted)]">
|
||||
{{ formatSample(metadataRecord.metadata) }}
|
||||
</pre>
|
||||
</UCard>
|
||||
</section>
|
||||
|
||||
<UCard v-if="request.type === 'data' || hasDataResponse">
|
||||
<UCard v-if="request.type === 'data' || hasDataResponse" 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">Datos</h2>
|
||||
<div class="flex flex-wrap gap-2 text-xs text-slate-300">
|
||||
<h2 class="text-lg font-semibold brand-section-title">Datos</h2>
|
||||
<div class="flex flex-wrap gap-2 text-xs text-[var(--brand-text-muted)]">
|
||||
<template v-if="dataStats">
|
||||
<UBadge color="primary">
|
||||
<span class="brand-pill inline-flex items-center gap-2 rounded-full px-3 py-1 text-xs">
|
||||
{{ dataStats.table }}: {{ dataStats.count }} registros (límite {{ dataStats.limit ?? 's/d' }})
|
||||
</UBadge>
|
||||
</span>
|
||||
</template>
|
||||
<template v-else-if="dataStatsCollection.length">
|
||||
<UBadge v-for="item in dataStatsCollection" :key="item.table" color="neutral">
|
||||
<span
|
||||
v-for="item in dataStatsCollection"
|
||||
:key="item.table"
|
||||
class="brand-pill inline-flex items-center gap-2 rounded-full px-3 py-1 text-xs"
|
||||
>
|
||||
{{ item.table }}: {{ item.count }} registros (límite {{ item.limit ?? 's/d' }})
|
||||
</UBadge>
|
||||
</span>
|
||||
</template>
|
||||
<UBadge v-else-if="tableData.length" color="neutral">
|
||||
<span v-else-if="tableData.length" class="brand-pill inline-flex items-center gap-2 rounded-full px-3 py-1 text-xs">
|
||||
{{ tableData.length }} registros visibles
|
||||
</UBadge>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="loading" class="flex items-center justify-center py-10">
|
||||
<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 v-if="loading" 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]">Procesando…</span>
|
||||
</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-[var(--brand-text-muted)]">
|
||||
Ejecuta una consulta de datos para ver resultados aquí.
|
||||
</div>
|
||||
<div v-else-if="tableData.length === 0" class="py-10 text-center text-sm text-slate-400">
|
||||
<div v-else-if="tableData.length === 0" class="py-10 text-center text-sm text-[var(--brand-text-muted)]">
|
||||
No se encontraron registros para los criterios seleccionados.
|
||||
</div>
|
||||
<div v-else class="overflow-auto">
|
||||
<table class="min-w-full divide-y divide-slate-800 text-sm">
|
||||
<thead class="bg-slate-900/60">
|
||||
<table class="brand-table min-w-full divide-y divide-[#3a2a16]/60 text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="column in visibleColumns" :key="column" class="px-4 py-2 text-left font-semibold">
|
||||
<th
|
||||
v-for="column in visibleColumns"
|
||||
:key="column"
|
||||
class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-[0.18em] text-[var(--brand-text-muted)]"
|
||||
>
|
||||
{{ column }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-800">
|
||||
<tr v-for="(row, index) in tableData" :key="index" class="hover:bg-slate-900/40">
|
||||
<td v-for="column in visibleColumns" :key="column" class="px-4 py-2">
|
||||
<tbody class="brand-table divide-y divide-[#3a2a16]/40">
|
||||
<tr v-for="(row, index) in tableData" :key="index" class="transition-colors">
|
||||
<td v-for="column in visibleColumns" :key="column" class="px-4 py-2 text-sm text-[var(--brand-text-muted)]">
|
||||
{{ formatCell(row[column]) }}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -229,11 +276,13 @@
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<UCard v-if="rawResponse">
|
||||
<UCard v-if="rawResponse" class="brand-card border border-transparent">
|
||||
<template #header>
|
||||
<h2 class="text-lg font-semibold">Respuesta cruda (JSON)</h2>
|
||||
<h2 class="text-lg font-semibold brand-section-title">Respuesta cruda (JSON)</h2>
|
||||
</template>
|
||||
<pre class="max-h-96 overflow-auto rounded bg-slate-900 p-4 text-sm">{{ formatSample(rawResponse) }}</pre>
|
||||
<pre class="max-h-96 overflow-auto rounded bg-[#22180f] p-4 text-sm text-[var(--brand-text-muted)]">
|
||||
{{ formatSample(rawResponse) }}
|
||||
</pre>
|
||||
</UCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -4,7 +4,103 @@ export default defineNuxtConfig({
|
||||
compatibilityDate: '2025-07-15',
|
||||
devtools: { enabled: true },
|
||||
css: ['~/assets/css/main.css'],
|
||||
modules: ['@nuxt/image', '@nuxt/ui', '@nuxt/test-utils'],
|
||||
modules: ['@nuxt/image', '@nuxt/ui', '@nuxt/test-utils', '@vite-pwa/nuxt'],
|
||||
app: {
|
||||
head: {
|
||||
link: [
|
||||
{ rel: 'icon', type: 'image/png', href: '/icons/icon-192.png' },
|
||||
{ rel: 'apple-touch-icon', sizes: '192x192', href: '/icons/icon-192.png' },
|
||||
{ rel: 'manifest', href: '/manifest.webmanifest' }
|
||||
],
|
||||
meta: [
|
||||
{ name: 'theme-color', content: '#14100b' },
|
||||
{ name: 'apple-mobile-web-app-capable', content: 'yes' },
|
||||
{ name: 'mobile-web-app-capable', content: 'yes' },
|
||||
{ name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' }
|
||||
]
|
||||
}
|
||||
},
|
||||
pwa: {
|
||||
registerType: 'autoUpdate',
|
||||
strategies: 'generateSW',
|
||||
manifestFilename: 'manifest.webmanifest',
|
||||
manifest: {
|
||||
name: 'Analítica Núcleo Data Studio',
|
||||
short_name: 'Analítica',
|
||||
description: 'Explora y valida tus tablas Supabase desde un único panel en modo lectura.',
|
||||
start_url: '/',
|
||||
scope: '/',
|
||||
display: 'standalone',
|
||||
background_color: '#1b1209',
|
||||
theme_color: '#c08040',
|
||||
icons: [
|
||||
{ src: '/icons/icon-192.png', sizes: '192x192', type: 'image/png' },
|
||||
{ src: '/icons/icon-512.png', sizes: '512x512', type: 'image/png' },
|
||||
{ src: '/icons/icon-192-maskable.png', sizes: '192x192', type: 'image/png', purpose: 'maskable' },
|
||||
{ src: '/icons/icon-512-maskable.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' },
|
||||
{ src: '/icons/icon-192-black.png', sizes: '192x192', type: 'image/png' },
|
||||
{ src: '/icons/icon-512-black.png', sizes: '512x512', type: 'image/png' }
|
||||
],
|
||||
screenshots: [
|
||||
{
|
||||
src: '/screenshots/screenshot-desktop.png',
|
||||
sizes: '2048x1041',
|
||||
type: 'image/png',
|
||||
form_factor: 'wide',
|
||||
label: 'Dashboard en escritorio'
|
||||
},
|
||||
{
|
||||
src: '/screenshots/screenshot-mobile.png',
|
||||
sizes: '774x1459',
|
||||
type: 'image/png',
|
||||
form_factor: 'narrow',
|
||||
label: 'Vista móvil'
|
||||
}
|
||||
]
|
||||
},
|
||||
workbox: {
|
||||
globPatterns: ['**/*.{js,css,html,png,svg,webp,ico,json,woff2}'],
|
||||
navigateFallback: '/',
|
||||
maximumFileSizeToCacheInBytes: 4 * 1024 * 1024,
|
||||
runtimeCaching: [
|
||||
{
|
||||
urlPattern: /^https:\/\/szesytydotpnuiuwybwb\.supabase\.co\//,
|
||||
handler: 'NetworkFirst',
|
||||
method: 'GET',
|
||||
options: {
|
||||
cacheName: 'supabase-data',
|
||||
networkTimeoutSeconds: 10,
|
||||
expiration: {
|
||||
maxEntries: 60,
|
||||
maxAgeSeconds: 3600
|
||||
},
|
||||
cacheableResponse: {
|
||||
statuses: [0, 200]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
client: {
|
||||
installPrompt: true,
|
||||
periodicSyncForUpdates: 3600
|
||||
},
|
||||
devOptions: {
|
||||
enabled: process.env.NODE_ENV === 'development',
|
||||
type: 'module'
|
||||
},
|
||||
includeAssets: [
|
||||
'favicon.ico',
|
||||
'icons/icon-192.png',
|
||||
'icons/icon-512.png',
|
||||
'icons/icon-192-maskable.png',
|
||||
'icons/icon-512-maskable.png',
|
||||
'icons/icon-192-black.png',
|
||||
'icons/icon-512-black.png',
|
||||
'screenshots/screenshot-desktop.png',
|
||||
'screenshots/screenshot-mobile.png'
|
||||
]
|
||||
},
|
||||
runtimeConfig: {
|
||||
supabase: {
|
||||
url: process.env.SUPABASE_URL || process.env.NEXT_PUBLIC_SUPABASE_URL,
|
||||
@@ -12,4 +108,4 @@ export default defineNuxtConfig({
|
||||
process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"dependencies": {
|
||||
"@nuxt/image": "^1.11.0",
|
||||
"@nuxt/test-utils": "^3.19.2",
|
||||
"@vite-pwa/nuxt": "^0.9.1",
|
||||
"@nuxt/ui": "^4.0.0",
|
||||
"@supabase/supabase-js": "^2.48.0",
|
||||
"nuxt": "^4.1.2",
|
||||
|
||||
BIN
nuxt4-app/public/icons/icon-192-black.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
nuxt4-app/public/icons/icon-192-maskable.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
nuxt4-app/public/icons/icon-192.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
nuxt4-app/public/icons/icon-512-black.png
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
nuxt4-app/public/icons/icon-512-maskable.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
nuxt4-app/public/icons/icon-512.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
nuxt4-app/public/logo.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
nuxt4-app/public/screenshots/screenshot-desktop.png
Normal file
|
After Width: | Height: | Size: 907 KiB |
BIN
nuxt4-app/public/screenshots/screenshot-mobile.png
Normal file
|
After Width: | Height: | Size: 379 KiB |