Files
analiticaNucleo/nuxt4-app/app/composables/useTheme.ts
josedario87 d9fefe6df4
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 49s
Feat: Agregar variable success y completar inputs de personalización de tema
Cambios realizados:
- Agregada variable success (#00dc82 Nuxt green) para botones de confirmación y elementos de éxito
- Agregados inputs de color para todas las variables de café (Uva, Oreado, Mojado, Verde)
- Agregados inputs de color para todas las variables de estados (Pendiente, Pagado, Anulado)
- Actualizada interfaz ThemeColors con nueva propiedad success
- Actualizados todos los temas preestablecidos (café, azul, verde, carbón) con variable success
- Actualizada validación de importación de temas con nueva propiedad
- Agregado CSS --brand-success en main.css

Los usuarios ahora pueden personalizar completamente todos los colores del sistema:
- 9 colores base (incluye success)
- 4 colores de tipos de café
- 3 colores de estados de pago
Total: 16 variables personalizables

La variable success se aplica a:
- Botones de confirmación
- Página seleccionada en sidebar
- Indicadores de éxito
2025-10-30 18:18:37 -06:00

281 lines
8.1 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 {
// Colores base
bg: string
surface: string
border: string
primary: string
primaryStrong: string
accent: string
success: string // Verde de éxito/confirmación
text: string
textMuted: string
// Colores para tipos de café
coffeeUva: string // Purple - Café Uva
coffeeOreado: string // Orange - Café Oreado
coffeeMojado: string // Cyan - Café Mojado
coffeeVerde: string // Green - Café Verde
// Colores para estados
statusPendiente: string // Amarillo/naranja - Pendiente de pago
statusPagado: string // Verde - Pagado
statusAnulado: string // Rojo/gris - Anulado
}
/**
* Tema por defecto - Café
*/
export const defaultTheme: ThemeColors = {
// Colores base
bg: '#14100b',
surface: '#1f180f',
border: '#3a2a16',
primary: '#e0c080',
primaryStrong: '#c08040',
accent: '#ffe0a0',
success: '#00dc82', // Nuxt green
text: '#fef9f0',
textMuted: '#d8c7a6',
// Colores para tipos de café
coffeeUva: '#a855f7', // Purple
coffeeOreado: '#f97316', // Orange
coffeeMojado: '#06b6d4', // Cyan
coffeeVerde: '#22c55e', // Green
// Colores para estados
statusPendiente: '#f59e0b', // Amber
statusPagado: '#10b981', // Emerald
statusAnulado: '#6b7280' // Gray
}
/**
* 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
// Colores base
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-success', colors.success)
root.style.setProperty('--brand-text', colors.text)
root.style.setProperty('--brand-text-muted', colors.textMuted)
// Colores para tipos de café
root.style.setProperty('--coffee-uva', colors.coffeeUva)
root.style.setProperty('--coffee-oreado', colors.coffeeOreado)
root.style.setProperty('--coffee-mojado', colors.coffeeMojado)
root.style.setProperty('--coffee-verde', colors.coffeeVerde)
// Colores para estados
root.style.setProperty('--status-pendiente', colors.statusPendiente)
root.style.setProperty('--status-pagado', colors.statusPagado)
root.style.setProperty('--status-anulado', colors.statusAnulado)
}
}
/**
* 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', 'success', 'text', 'textMuted',
'coffeeUva', 'coffeeOreado', 'coffeeMojado', 'coffeeVerde',
'statusPendiente', 'statusPagado', 'statusAnulado'
]
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
}
}