Roles ampliados (10) y matriz de permisos por rol
- Roles: admin, médico, recepción, enfermería, cosmetología, psicóloga, farmacia, entrenador, marketing + lectura (compatibilidad) - Matriz de permisos por rol persistida en Odoo (ir.config_parameter) y editable en Configuración → Roles (secciones × roles) - Prioridad: permisos personales del usuario > matriz por rol > defaults - Protección: admin siempre conserva /configuracion
This commit is contained in:
@@ -4,12 +4,19 @@ import { odooApi, type FrontendUser, type FrontendRole } from '../services/odoo'
|
||||
|
||||
const TOKEN_KEY = 'skeen_token';
|
||||
const USER_KEY = 'skeen_user';
|
||||
const MENU_ROLES_KEY = 'skeen_menu_roles';
|
||||
|
||||
const ROLE_RANK: Record<FrontendRole, number> = {
|
||||
lectura: 10,
|
||||
admin: 100,
|
||||
medico: 50,
|
||||
recepcion: 50,
|
||||
admin: 100,
|
||||
enfermeria: 50,
|
||||
cosmetologia: 50,
|
||||
psicologa: 50,
|
||||
farmacia: 50,
|
||||
entrenador: 10,
|
||||
marketing: 10,
|
||||
lectura: 10,
|
||||
};
|
||||
|
||||
// Visibilidad del menú por ruta. Si una ruta no está listada, se muestra a todos los roles.
|
||||
@@ -60,12 +67,35 @@ export const AuthProvider: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const [user, setUser] = useState<FrontendUser | null>(null);
|
||||
const [token, setToken] = useState<string | null>(() => localStorage.getItem(TOKEN_KEY));
|
||||
const [loading, setLoading] = useState(true);
|
||||
// Matriz dinámica de permisos por rol (cache en localStorage; fallback a MENU_ROLES)
|
||||
const [menuRoles, setMenuRoles] = useState<Record<string, FrontendRole[]> | null>(() => {
|
||||
try {
|
||||
const cached = localStorage.getItem(MENU_ROLES_KEY);
|
||||
return cached ? JSON.parse(cached) : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
const refreshMenuRoles = useCallback(async () => {
|
||||
try {
|
||||
const res = await odooApi.getRolePermissions();
|
||||
if (res.status === 'success' && res.matrix) {
|
||||
setMenuRoles(res.matrix);
|
||||
localStorage.setItem(MENU_ROLES_KEY, JSON.stringify(res.matrix));
|
||||
}
|
||||
} catch {
|
||||
// fallback: MENU_ROLES hardcodeado
|
||||
}
|
||||
}, []);
|
||||
|
||||
const clear = useCallback(() => {
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
localStorage.removeItem(USER_KEY);
|
||||
localStorage.removeItem(MENU_ROLES_KEY);
|
||||
setToken(null);
|
||||
setUser(null);
|
||||
setMenuRoles(null);
|
||||
}, []);
|
||||
|
||||
// Validar token al arrancar
|
||||
@@ -87,6 +117,7 @@ export const AuthProvider: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
if (res.status === 'success') {
|
||||
setUser(res.user);
|
||||
localStorage.setItem(USER_KEY, JSON.stringify(res.user));
|
||||
refreshMenuRoles();
|
||||
} else {
|
||||
clear();
|
||||
}
|
||||
@@ -94,7 +125,7 @@ export const AuthProvider: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
.catch(() => { if (alive) clear(); })
|
||||
.finally(() => { if (alive) setLoading(false); });
|
||||
return () => { alive = false; };
|
||||
}, [clear]);
|
||||
}, [clear, refreshMenuRoles]);
|
||||
|
||||
const login = async (login: string, password: string): Promise<FrontendUser> => {
|
||||
const res = await odooApi.login(login, password);
|
||||
@@ -105,6 +136,7 @@ export const AuthProvider: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
localStorage.setItem(USER_KEY, JSON.stringify(res.user));
|
||||
setToken(res.token);
|
||||
setUser(res.user);
|
||||
refreshMenuRoles();
|
||||
return res.user;
|
||||
};
|
||||
|
||||
@@ -120,11 +152,11 @@ export const AuthProvider: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
|
||||
const canSee = (href: string): boolean => {
|
||||
if (!user) return false;
|
||||
// Permisos personalizados: lista no vacía manda sobre los defaults por rol
|
||||
// Permisos personalizados: lista no vacía manda sobre la matriz por rol
|
||||
if (user.allowed_menus && user.allowed_menus.length > 0) {
|
||||
return user.allowed_menus.includes(href);
|
||||
}
|
||||
const allowed = MENU_ROLES[href];
|
||||
const allowed = (menuRoles || MENU_ROLES)[href];
|
||||
if (!allowed) return true;
|
||||
return allowed.includes(user.role);
|
||||
};
|
||||
@@ -144,7 +176,13 @@ export const useAuth = (): AuthContextValue => {
|
||||
|
||||
export const ROLE_LABELS: Record<FrontendRole, string> = {
|
||||
admin: 'Administrador',
|
||||
recepcion: 'Recepción',
|
||||
medico: 'Médico',
|
||||
lectura: 'Solo lectura',
|
||||
recepcion: 'Recepción',
|
||||
enfermeria: 'Enfermería',
|
||||
cosmetologia: 'Cosmetología',
|
||||
psicologa: 'Psicóloga',
|
||||
farmacia: 'Farmacia',
|
||||
entrenador: 'Entrenador',
|
||||
marketing: 'Marketing',
|
||||
lectura: 'Lectura',
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user