- Módulo Visitas completo (auto desde agenda, insumos con descargo de inventario, fotos antes/después y documentos, receta imprimible) - Punto de Venta (catálogo + ticket sticky, cobro con cambio, pago con puntos monedero, ticket imprimible) - WACRM: leads automáticos desde WhatsApp, asignación de conversaciones y leads a agentes, conversión lead→paciente, ficha del paciente en chat - Pacientes: completitud de expediente, alertas clínicas, historial unificado con detalle, foto, WhatsApp, estado de cuenta, filtros rápidos (VIP/recientes/médico), documentos (expediente escaneado + galería) - Agenda: vistas por médico y por hora, filtros rápidos (libres, primera vez, check-in, no-show), modal de acciones, bloqueos por médico, drag&drop para mover citas - Reportes: 18 pestañas (diario, cortes, ingresos, inventario, adeudos, comisiones, pagos, devoluciones, top clientes, horas, paquetes, vendedores, concentrado, recomendaciones, KPIs) con exportación Excel - Temas: nuevo tema Clásico (look legacy AdminLTE) con submenús tipo treeview, selector de tema; accesos rápidos personalizables con 3 presentaciones; búsqueda global; notificaciones reales - Configuración: secciones (clínica, usuarios con permisos por sección, recetas, catálogos de diagnósticos y procedimientos) - Inventario: alertas de caducidad y sugerencia de compra, cron diario que descuenta artículos caducados, compras/bajas - Consultas Médicas, página Expedientes, importadores delta (citas/visitas legacy idempotentes), depuración de duplicados - Infra: tema Tailwind conectado (@config), gzip en nginx, secuencias Odoo corregidas (noupdate, company_id), rollback en validaciones
55 lines
1.6 KiB
TypeScript
55 lines
1.6 KiB
TypeScript
import { createContext, useContext, useEffect, useState, type ReactNode } from 'react';
|
|
|
|
export type Theme = 'skeen' | 'homenest' | 'clasico';
|
|
|
|
interface ThemeContextValue {
|
|
theme: Theme;
|
|
setTheme: (theme: Theme) => void;
|
|
toggleTheme: () => void;
|
|
}
|
|
|
|
const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
|
|
|
|
const STORAGE_KEY = 'skeen-theme';
|
|
const THEME_ORDER: Theme[] = ['skeen', 'homenest', 'clasico'];
|
|
|
|
function getInitialTheme(): Theme {
|
|
if (typeof window === 'undefined') return 'skeen';
|
|
const stored = window.localStorage.getItem(STORAGE_KEY) as Theme | null;
|
|
if (stored === 'skeen' || stored === 'homenest' || stored === 'clasico') return stored;
|
|
return 'skeen';
|
|
}
|
|
|
|
export function ThemeProvider({ children }: { children: ReactNode }) {
|
|
const [theme, setThemeState] = useState<Theme>(getInitialTheme);
|
|
|
|
const setTheme = (next: Theme) => {
|
|
setThemeState(next);
|
|
if (typeof window !== 'undefined') {
|
|
window.localStorage.setItem(STORAGE_KEY, next);
|
|
document.documentElement.setAttribute('data-theme', next);
|
|
}
|
|
};
|
|
|
|
const toggleTheme = () => {
|
|
const idx = THEME_ORDER.indexOf(theme);
|
|
setTheme(THEME_ORDER[(idx + 1) % THEME_ORDER.length]);
|
|
};
|
|
|
|
useEffect(() => {
|
|
document.documentElement.setAttribute('data-theme', theme);
|
|
}, [theme]);
|
|
|
|
return (
|
|
<ThemeContext.Provider value={{ theme, setTheme, toggleTheme }}>
|
|
{children}
|
|
</ThemeContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useTheme() {
|
|
const ctx = useContext(ThemeContext);
|
|
if (!ctx) throw new Error('useTheme must be used within ThemeProvider');
|
|
return ctx;
|
|
}
|