Files
analiticaNucleo/nuxt4-app/app/composables/useTheme.ts
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

240 lines
6.5 KiB
TypeScript

/**
* Composable para gestionar el sistema de temas de Analítica Núcleo
*
* Proporciona funciones para cargar, aplicar, guardar y resetear temas personalizados.
* El tema se guarda en localStorage y se sincroniza entre pestañas del navegador.
*
* @example
* ```ts
* const { theme, applyTheme, loadTheme, saveTheme, resetTheme } = useTheme()
*
* // Cargar tema guardado
* onMounted(() => loadTheme())
*
* // Modificar tema
* theme.value.primary = '#ff5733'
* applyTheme()
* saveTheme()
* ```
*/
export interface ThemeColors {
bg: string
surface: string
border: string
primary: string
primaryStrong: string
accent: string
text: string
textMuted: string
}
/**
* Tema por defecto - Café
*/
export const defaultTheme: ThemeColors = {
bg: '#14100b',
surface: '#1f180f',
border: '#3a2a16',
primary: '#e0c080',
primaryStrong: '#c08040',
accent: '#ffe0a0',
text: '#fef9f0',
textMuted: '#d8c7a6'
}
/**
* Clave de localStorage donde se guarda el tema
*/
const STORAGE_KEY = 'custom-theme'
/**
* Hook principal del sistema de temas
*/
export const useTheme = () => {
// Estado reactivo compartido entre todos los componentes
const theme = useState<ThemeColors>('app-theme', () => ({ ...defaultTheme }))
/**
* Aplica las variables CSS del tema al DOM
* @param themeColors - Objeto con los colores del tema (opcional, usa el estado actual si no se proporciona)
*/
const applyTheme = (themeColors?: ThemeColors) => {
if (import.meta.client) {
const colors = themeColors || theme.value
const root = document.documentElement
root.style.setProperty('--brand-bg', colors.bg)
root.style.setProperty('--brand-surface', colors.surface)
root.style.setProperty('--brand-border', colors.border)
root.style.setProperty('--brand-primary', colors.primary)
root.style.setProperty('--brand-primary-strong', colors.primaryStrong)
root.style.setProperty('--brand-accent', colors.accent)
root.style.setProperty('--brand-text', colors.text)
root.style.setProperty('--brand-text-muted', colors.textMuted)
}
}
/**
* Carga el tema guardado desde localStorage
* Si no existe tema guardado, aplica el tema por defecto
*/
const loadTheme = () => {
if (import.meta.client) {
try {
const savedTheme = localStorage.getItem(STORAGE_KEY)
if (savedTheme) {
const parsed = JSON.parse(savedTheme) as ThemeColors
theme.value = parsed
applyTheme(parsed)
} else {
// No hay tema guardado, aplicar el por defecto
applyTheme(defaultTheme)
}
} catch (error) {
console.error('Error al cargar el tema desde localStorage:', error)
// En caso de error, aplicar tema por defecto
applyTheme(defaultTheme)
}
}
}
/**
* Guarda el tema actual en localStorage
* @returns true si se guardó correctamente, false si hubo un error
*/
const saveTheme = (): boolean => {
if (import.meta.client) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(theme.value))
return true
} catch (error) {
console.error('Error al guardar el tema en localStorage:', error)
return false
}
}
return false
}
/**
* Resetea el tema a los valores por defecto y limpia localStorage
*/
const resetTheme = () => {
theme.value = { ...defaultTheme }
applyTheme(defaultTheme)
if (import.meta.client) {
try {
localStorage.removeItem(STORAGE_KEY)
} catch (error) {
console.error('Error al limpiar el tema de localStorage:', error)
}
}
}
/**
* Establece un nuevo tema completo
* @param newTheme - Objeto con los colores del nuevo tema
* @param save - Si true, guarda automáticamente en localStorage (default: false)
*/
const setTheme = (newTheme: ThemeColors, save = false) => {
theme.value = { ...newTheme }
applyTheme(newTheme)
if (save) {
saveTheme()
}
}
/**
* Exporta el tema actual como JSON string
* @returns JSON string del tema actual
*/
const exportTheme = (): string => {
return JSON.stringify(theme.value, null, 2)
}
/**
* Importa un tema desde JSON string
* @param jsonString - JSON string con la configuración del tema
* @param save - Si true, guarda automáticamente en localStorage (default: false)
* @returns true si se importó correctamente, false si hubo un error
*/
const importTheme = (jsonString: string, save = false): boolean => {
try {
const parsed = JSON.parse(jsonString) as ThemeColors
// Validar que tenga todas las propiedades requeridas
const requiredKeys: (keyof ThemeColors)[] = [
'bg', 'surface', 'border', 'primary',
'primaryStrong', 'accent', 'text', 'textMuted'
]
for (const key of requiredKeys) {
if (!parsed[key] || typeof parsed[key] !== 'string') {
throw new Error(`Propiedad "${key}" faltante o inválida`)
}
}
setTheme(parsed, save)
return true
} catch (error) {
console.error('Error al importar el tema:', error)
return false
}
}
/**
* Listener para sincronizar cambios de tema entre pestañas
* Se ejecuta automáticamente cuando se detecta un cambio en localStorage
*/
const syncThemeAcrossTabs = (event: StorageEvent) => {
if (event.key === STORAGE_KEY && event.newValue) {
try {
const newTheme = JSON.parse(event.newValue) as ThemeColors
theme.value = newTheme
applyTheme(newTheme)
} catch (error) {
console.error('Error al sincronizar tema entre pestañas:', error)
}
} else if (event.key === STORAGE_KEY && event.newValue === null) {
// El tema fue eliminado en otra pestaña, resetear
resetTheme()
}
}
/**
* Inicializa los listeners de sincronización entre pestañas
* Solo debe llamarse una vez al montar la aplicación
*/
const initStorageListener = () => {
if (import.meta.client) {
window.addEventListener('storage', syncThemeAcrossTabs)
}
}
/**
* Limpia los listeners de sincronización
* Debe llamarse al desmontar si es necesario
*/
const cleanupStorageListener = () => {
if (import.meta.client) {
window.removeEventListener('storage', syncThemeAcrossTabs)
}
}
return {
theme,
defaultTheme,
applyTheme,
loadTheme,
saveTheme,
resetTheme,
setTheme,
exportTheme,
importTheme,
initStorageListener,
cleanupStorageListener
}
}