Files
analiticaNucleo/nuxt4-app/app/pages/settings.vue
josedario87 63c7043664
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 47s
Feat: Agregar botones de Copiar Texto y Copiar JSON
Implementa funcionalidad de copia en tres secciones del Informe:

📋 Funcionalidades agregadas:
1. Lista de Ingresos
   - Copiar Texto: Formato WhatsApp con emojis y legible
   - Copiar JSON: Formato estructurado para sistemas

2. Top 10 Clientes
   - Copiar Texto: Ranking formateado con métricas
   - Copiar JSON: Array de objetos con datos completos

3. Serie Temporal Acumulada
   - Copiar Texto: Evolución temporal con emojis
   - Copiar JSON: Datos completos para análisis

 Características:
- Botones con iconos (i-lucide-copy y i-lucide-code)
- Disabled cuando no hay datos disponibles
- Alertas de confirmación al copiar
- Formato texto optimizado para WhatsApp
- Incluye metadata: rango de fechas y timestamp

Uso:
- Copiar Texto → Compartir en WhatsApp/Telegram
- Copiar JSON → Integración con otros sistemas
2025-10-30 16:33:54 -06:00

535 lines
19 KiB
Vue

<script setup lang="ts">
import { ref, watch } from 'vue'
import type { ThemeColors } from '~/composables/useTheme'
definePageMeta({
layout: 'dashboard',
title: 'Configuración'
})
// Usar el composable de temas
const {
theme,
defaultTheme,
applyTheme,
saveTheme: saveThemeComposable,
resetTheme: resetThemeComposable,
exportTheme,
importTheme
} = useTheme()
// Temas predefinidos
const presetThemes: Record<string, ThemeColors> = {
cafe: { ...defaultTheme },
azul: {
bg: '#0a0e1a',
surface: '#151a28',
border: '#2d3748',
primary: '#60a5fa',
primaryStrong: '#3b82f6',
accent: '#93c5fd',
text: '#f0f4f8',
textMuted: '#cbd5e1'
},
verde: {
bg: '#0f1a14',
surface: '#1a2820',
border: '#2d4033',
primary: '#86efac',
primaryStrong: '#4ade80',
accent: '#bbf7d0',
text: '#f0fdf4',
textMuted: '#d1fae5'
},
carbon: {
bg: '#0f0f0f',
surface: '#1a1a1a',
border: '#333333',
primary: '#a3a3a3',
primaryStrong: '#737373',
accent: '#d4d4d4',
text: '#fafafa',
textMuted: '#d4d4d4'
}
}
// Aplicar preset de tema
const applyPreset = (presetName: keyof typeof presetThemes) => {
const preset = presetThemes[presetName]
theme.value = { ...preset }
applyTheme(theme.value)
useToast().add({
title: 'Tema aplicado',
description: `Se aplicó el tema ${presetName}`,
color: 'blue'
})
}
// Guardar tema con feedback
const handleSaveTheme = () => {
const success = saveThemeComposable()
if (success) {
useToast().add({
title: 'Tema guardado',
description: 'Los cambios se aplicaron correctamente',
color: 'green'
})
} else {
useToast().add({
title: 'Error al guardar',
description: 'No se pudo guardar el tema',
color: 'red'
})
}
}
// Resetear al tema por defecto
const handleResetTheme = () => {
resetThemeComposable()
useToast().add({
title: 'Tema reseteado',
description: 'Se restauraron los colores por defecto',
color: 'blue'
})
}
// Exportar tema al portapapeles
const handleExportTheme = async () => {
try {
const themeJson = exportTheme()
await navigator.clipboard.writeText(themeJson)
useToast().add({
title: 'Tema exportado',
description: 'El JSON del tema se copió al portapapeles',
color: 'green'
})
} catch (error) {
useToast().add({
title: 'Error al exportar',
description: 'No se pudo copiar al portapapeles',
color: 'red'
})
}
}
// Importar tema desde JSON
const importJsonInput = ref('')
const showImportModal = ref(false)
const handleImportTheme = () => {
const success = importTheme(importJsonInput.value, false)
if (success) {
useToast().add({
title: 'Tema importado',
description: 'El tema se aplicó correctamente',
color: 'green'
})
showImportModal.value = false
importJsonInput.value = ''
} else {
useToast().add({
title: 'Error al importar',
description: 'El JSON proporcionado no es válido',
color: 'red'
})
}
}
// Vista previa en tiempo real (activada por defecto)
const livePreview = ref(true)
watch(() => theme.value, (newTheme) => {
if (livePreview.value) {
applyTheme(newTheme)
}
}, { deep: true })
</script>
<template>
<UDashboardLayout>
<UDashboardPanel grow>
<UDashboardNavbar
title="Configuración"
description="Personaliza tu experiencia y preferencias del sistema"
/>
<UDashboardPanelContent>
<div class="max-w-4xl mx-auto space-y-8">
<!-- Configuración de Tema -->
<UCard>
<template #header>
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-[var(--brand-primary)]/20 flex items-center justify-center">
<UIcon name="i-lucide-palette" class="size-5 text-[var(--brand-primary)]" />
</div>
<div>
<h3 class="font-semibold text-[var(--brand-text)]">
Tema y Apariencia
</h3>
<p class="text-sm text-[var(--brand-text-muted)]">
Personaliza los colores de la interfaz
</p>
</div>
</div>
<UToggle v-model="livePreview" label="Vista previa en vivo" />
</div>
</template>
<!-- Temas Predefinidos -->
<div class="mb-6 pb-6 border-b border-[var(--brand-border)]">
<label class="block text-sm font-medium text-[var(--brand-text)] mb-3">
Temas Predefinidos
</label>
<div class="grid grid-cols-2 md:grid-cols-4 gap-3">
<button
@click="applyPreset('cafe')"
class="p-4 rounded-lg border-2 border-[var(--brand-border)] hover:border-[var(--brand-primary)] transition-colors group"
>
<div class="flex gap-2 mb-2">
<div class="w-6 h-6 rounded" style="background: #e0c080" />
<div class="w-6 h-6 rounded" style="background: #1f180f" />
</div>
<p class="text-xs text-[var(--brand-text)] font-medium group-hover:text-[var(--brand-primary)]">Café</p>
</button>
<button
@click="applyPreset('azul')"
class="p-4 rounded-lg border-2 border-[var(--brand-border)] hover:border-[var(--brand-primary)] transition-colors group"
>
<div class="flex gap-2 mb-2">
<div class="w-6 h-6 rounded" style="background: #60a5fa" />
<div class="w-6 h-6 rounded" style="background: #151a28" />
</div>
<p class="text-xs text-[var(--brand-text)] font-medium group-hover:text-[var(--brand-primary)]">Azul</p>
</button>
<button
@click="applyPreset('verde')"
class="p-4 rounded-lg border-2 border-[var(--brand-border)] hover:border-[var(--brand-primary)] transition-colors group"
>
<div class="flex gap-2 mb-2">
<div class="w-6 h-6 rounded" style="background: #86efac" />
<div class="w-6 h-6 rounded" style="background: #1a2820" />
</div>
<p class="text-xs text-[var(--brand-text)] font-medium group-hover:text-[var(--brand-primary)]">Verde</p>
</button>
<button
@click="applyPreset('carbon')"
class="p-4 rounded-lg border-2 border-[var(--brand-border)] hover:border-[var(--brand-primary)] transition-colors group"
>
<div class="flex gap-2 mb-2">
<div class="w-6 h-6 rounded" style="background: #a3a3a3" />
<div class="w-6 h-6 rounded" style="background: #1a1a1a" />
</div>
<p class="text-xs text-[var(--brand-text)] font-medium group-hover:text-[var(--brand-primary)]">Carbón</p>
</button>
</div>
</div>
<div class="space-y-6">
<!-- Fondo Principal -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-[var(--brand-text)] mb-2">
Fondo Principal
</label>
<div class="flex gap-2">
<input
v-model="theme.bg"
type="color"
class="h-10 w-20 rounded-lg border border-[var(--brand-border)] cursor-pointer"
/>
<UInput
v-model="theme.bg"
placeholder="#14100b"
class="flex-1"
/>
</div>
</div>
<div>
<label class="block text-sm font-medium text-[var(--brand-text)] mb-2">
Fondo Secundario (Surface)
</label>
<div class="flex gap-2">
<input
v-model="theme.surface"
type="color"
class="h-10 w-20 rounded-lg border border-[var(--brand-border)] cursor-pointer"
/>
<UInput
v-model="theme.surface"
placeholder="#1f180f"
class="flex-1"
/>
</div>
</div>
</div>
<!-- Bordes y Primario -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-[var(--brand-text)] mb-2">
Color de Bordes
</label>
<div class="flex gap-2">
<input
v-model="theme.border"
type="color"
class="h-10 w-20 rounded-lg border border-[var(--brand-border)] cursor-pointer"
/>
<UInput
v-model="theme.border"
placeholder="#3a2a16"
class="flex-1"
/>
</div>
</div>
<div>
<label class="block text-sm font-medium text-[var(--brand-text)] mb-2">
Color Primario
</label>
<div class="flex gap-2">
<input
v-model="theme.primary"
type="color"
class="h-10 w-20 rounded-lg border border-[var(--brand-border)] cursor-pointer"
/>
<UInput
v-model="theme.primary"
placeholder="#e0c080"
class="flex-1"
/>
</div>
</div>
</div>
<!-- Primario Fuerte y Acento -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-[var(--brand-text)] mb-2">
Color Primario Fuerte
</label>
<div class="flex gap-2">
<input
v-model="theme.primaryStrong"
type="color"
class="h-10 w-20 rounded-lg border border-[var(--brand-border)] cursor-pointer"
/>
<UInput
v-model="theme.primaryStrong"
placeholder="#c08040"
class="flex-1"
/>
</div>
</div>
<div>
<label class="block text-sm font-medium text-[var(--brand-text)] mb-2">
Color de Acento
</label>
<div class="flex gap-2">
<input
v-model="theme.accent"
type="color"
class="h-10 w-20 rounded-lg border border-[var(--brand-border)] cursor-pointer"
/>
<UInput
v-model="theme.accent"
placeholder="#ffe0a0"
class="flex-1"
/>
</div>
</div>
</div>
<!-- Texto y Texto Muted -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-[var(--brand-text)] mb-2">
Color de Texto
</label>
<div class="flex gap-2">
<input
v-model="theme.text"
type="color"
class="h-10 w-20 rounded-lg border border-[var(--brand-border)] cursor-pointer"
/>
<UInput
v-model="theme.text"
placeholder="#fef9f0"
class="flex-1"
/>
</div>
</div>
<div>
<label class="block text-sm font-medium text-[var(--brand-text)] mb-2">
Color de Texto Secundario
</label>
<div class="flex gap-2">
<input
v-model="theme.textMuted"
type="color"
class="h-10 w-20 rounded-lg border border-[var(--brand-border)] cursor-pointer"
/>
<UInput
v-model="theme.textMuted"
placeholder="#d8c7a6"
class="flex-1"
/>
</div>
</div>
</div>
<!-- Acciones -->
<div class="space-y-3 pt-4 border-t border-[var(--brand-border)]">
<!-- Botones principales -->
<div class="flex justify-between items-center">
<UButton
color="neutral"
variant="ghost"
icon="i-lucide-rotate-ccw"
@click="handleResetTheme"
>
Restaurar por defecto
</UButton>
<UButton
color="primary"
icon="i-lucide-save"
@click="handleSaveTheme"
>
Guardar cambios
</UButton>
</div>
<!-- Botones de import/export -->
<div class="flex gap-2">
<UButton
color="neutral"
variant="outline"
icon="i-lucide-download"
@click="handleExportTheme"
class="flex-1"
>
Exportar tema
</UButton>
<UButton
color="neutral"
variant="outline"
icon="i-lucide-upload"
@click="showImportModal = true"
class="flex-1"
>
Importar tema
</UButton>
</div>
</div>
</div>
</UCard>
<!-- Modal de Importar Tema -->
<UModal v-model="showImportModal" title="Importar Tema">
<div class="p-4 space-y-4">
<div>
<label class="block text-sm font-medium text-[var(--brand-text)] mb-2">
Pega el JSON del tema
</label>
<UTextarea
v-model="importJsonInput"
:rows="10"
placeholder='{\n "bg": "#14100b",\n "surface": "#1f180f",\n ...\n}'
class="font-mono text-xs"
/>
</div>
<div class="flex justify-end gap-2">
<UButton
color="neutral"
variant="ghost"
@click="showImportModal = false"
>
Cancelar
</UButton>
<UButton
color="primary"
@click="handleImportTheme"
>
Importar
</UButton>
</div>
</div>
</UModal>
<!-- Vista previa de colores -->
<UCard>
<template #header>
<div class="flex items-center gap-3">
<UIcon name="i-lucide-eye" class="size-5 text-[var(--brand-primary)]" />
<h3 class="font-semibold text-[var(--brand-text)]">
Vista Previa
</h3>
</div>
</template>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<div class="space-y-2">
<div class="h-20 rounded-lg border-2 border-[var(--brand-border)]" :style="{ backgroundColor: theme.bg }" />
<p class="text-xs text-center text-[var(--brand-text-muted)]">Fondo Principal</p>
</div>
<div class="space-y-2">
<div class="h-20 rounded-lg border-2 border-[var(--brand-border)]" :style="{ backgroundColor: theme.surface }" />
<p class="text-xs text-center text-[var(--brand-text-muted)]">Surface</p>
</div>
<div class="space-y-2">
<div class="h-20 rounded-lg border-2 border-[var(--brand-border)]" :style="{ backgroundColor: theme.primary }" />
<p class="text-xs text-center text-[var(--brand-text-muted)]">Primario</p>
</div>
<div class="space-y-2">
<div class="h-20 rounded-lg border-2 border-[var(--brand-border)]" :style="{ backgroundColor: theme.accent }" />
<p class="text-xs text-center text-[var(--brand-text-muted)]">Acento</p>
</div>
</div>
</UCard>
<!-- Otras configuraciones (placeholder) -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<UCard>
<template #header>
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-amber-50 dark:bg-amber-950/30 flex items-center justify-center">
<UIcon name="i-lucide-bell" class="size-5 text-amber-600 dark:text-amber-400" />
</div>
<h3 class="font-semibold text-[var(--brand-text)]">
Notificaciones
</h3>
</div>
</template>
<p class="text-sm text-[var(--brand-text-muted)]">
Gestión de alertas y comunicaciones (Próximamente)
</p>
</UCard>
<UCard>
<template #header>
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-green-50 dark:bg-green-950/30 flex items-center justify-center">
<UIcon name="i-lucide-shield" class="size-5 text-green-600 dark:text-green-400" />
</div>
<h3 class="font-semibold text-[var(--brand-text)]">
Privacidad
</h3>
</div>
</template>
<p class="text-sm text-[var(--brand-text-muted)]">
Control de datos y seguridad (Próximamente)
</p>
</UCard>
</div>
</div>
</UDashboardPanelContent>
</UDashboardPanel>
</UDashboardLayout>
</template>