feature/rbac y perfiles implementados
This commit is contained in:
@@ -1,23 +1,21 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useUser } from '../context/UserContext';
|
||||
import { fetchWithAuth } from '../fetchWithAuth';
|
||||
|
||||
export default function Sidebar({ isMobileOpen, onMobileClose }) {
|
||||
// Leer si el usuario es importador desde localStorage
|
||||
const isImportador = typeof window !== 'undefined' && localStorage.getItem('user_is_importador') === 'true';
|
||||
// Leer grupos del usuario desde localStorage
|
||||
let userGroups = [];
|
||||
if (typeof window !== 'undefined') {
|
||||
try {
|
||||
userGroups = JSON.parse(localStorage.getItem('user_groups') || '[]');
|
||||
} catch {
|
||||
userGroups = [];
|
||||
}
|
||||
}
|
||||
// Si los grupos son exactamente [3,5]
|
||||
const isGroup35 = Array.isArray(userGroups) && userGroups.length === 2 && userGroups.includes(3) && userGroups.includes(5);
|
||||
// Leer DEBUG_MODE desde variables de entorno
|
||||
const isDebugMode = import.meta.env.VITE_DEBUG_MODE === 'true';
|
||||
|
||||
// Permisos RBAC — cargados desde /rbac/my-permissions/ al hacer login
|
||||
const [userPermissions, setUserPermissions] = useState(() => {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem('user_permissions') || '[]');
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
const hasPermission = (codename) => userPermissions.includes(codename);
|
||||
|
||||
// Estados para responsividad
|
||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||
@@ -34,6 +32,14 @@ export default function Sidebar({ isMobileOpen, onMobileClose }) {
|
||||
const handleLogout = () => {
|
||||
localStorage.removeItem('access');
|
||||
localStorage.removeItem('refresh');
|
||||
localStorage.removeItem('user_id');
|
||||
localStorage.removeItem('user_is_importador');
|
||||
localStorage.removeItem('user_groups');
|
||||
localStorage.removeItem('user_permissions');
|
||||
localStorage.removeItem('username');
|
||||
localStorage.removeItem('user_email');
|
||||
localStorage.removeItem('user_first_name');
|
||||
localStorage.removeItem('user_last_name');
|
||||
window.dispatchEvent(new CustomEvent('authStateChanged'));
|
||||
navigate('/login');
|
||||
};
|
||||
@@ -55,6 +61,22 @@ export default function Sidebar({ isMobileOpen, onMobileClose }) {
|
||||
handleMobileClose();
|
||||
}, [location.pathname]);
|
||||
|
||||
// Si no hay permisos en localStorage (sesión previa al RBAC), los carga del servidor
|
||||
useEffect(() => {
|
||||
if (userPermissions.length === 0 && localStorage.getItem('access')) {
|
||||
const apiUrl = import.meta.env.VITE_EFC_API_URL || '';
|
||||
fetchWithAuth(`${apiUrl}/rbac/my-permissions/`)
|
||||
.then(res => res.ok ? res.json() : null)
|
||||
.then(data => {
|
||||
if (data && Array.isArray(data.permissions)) {
|
||||
localStorage.setItem('user_permissions', JSON.stringify(data.permissions));
|
||||
setUserPermissions(data.permissions);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
}, []);
|
||||
|
||||
// El usuario y loading ahora vienen del contexto global
|
||||
|
||||
// Definir todas las secciones
|
||||
@@ -71,9 +93,8 @@ export default function Sidebar({ isMobileOpen, onMobileClose }) {
|
||||
</svg>
|
||||
)
|
||||
},
|
||||
// Ocultar 'Mi Organización' si es importador o si esGroup35
|
||||
...(
|
||||
(!isImportador && !isGroup35)
|
||||
hasPermission('organizacion.view')
|
||||
? [
|
||||
{
|
||||
name: 'Mi Organización',
|
||||
@@ -101,15 +122,19 @@ export default function Sidebar({ isMobileOpen, onMobileClose }) {
|
||||
</svg>
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'Auditor',
|
||||
path: '/auditor',
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
...(
|
||||
hasPermission('auditoria.view')
|
||||
? [{
|
||||
name: 'Auditor',
|
||||
path: '/auditor',
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
|
||||
</svg>
|
||||
)
|
||||
}]
|
||||
: []
|
||||
)
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -158,9 +183,9 @@ export default function Sidebar({ isMobileOpen, onMobileClose }) {
|
||||
}
|
||||
]
|
||||
},
|
||||
// Nueva sección Tableros - Solo mostrar si DEBUG_MODE es true
|
||||
// Nueva sección Tableros - Solo mostrar si DEBUG_MODE es true y tiene cards.view
|
||||
...(
|
||||
(isDebugMode && !isGroup35)
|
||||
(isDebugMode && hasPermission('cards.view'))
|
||||
? [
|
||||
{
|
||||
title: 'Tableros',
|
||||
@@ -179,71 +204,70 @@ export default function Sidebar({ isMobileOpen, onMobileClose }) {
|
||||
]
|
||||
: []
|
||||
),
|
||||
...(
|
||||
isGroup35
|
||||
? []
|
||||
: [
|
||||
{
|
||||
title: 'Acceso a Usuarios',
|
||||
items: [
|
||||
// Botón Importadores como primer elemento
|
||||
{
|
||||
name: 'Importadores',
|
||||
path: '/importers',
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M16 7a4 4 0 01-8 0M12 11v10m-6 0h12a2 2 0 002-2v-5a2 2 0 00-2-2H6a2 2 0 00-2 2v5a2 2 0 002 2z" />
|
||||
</svg>
|
||||
)
|
||||
},
|
||||
...(
|
||||
isImportador
|
||||
? []
|
||||
: [
|
||||
{
|
||||
name: 'Usuarios',
|
||||
path: '/users',
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
]
|
||||
),
|
||||
{
|
||||
name: 'Ventanilla Única',
|
||||
path: '/vucem',
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="2" fill="none" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 12h8M12 8v8" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
)
|
||||
{
|
||||
title: 'Acceso a Usuarios',
|
||||
items: [
|
||||
...(
|
||||
hasPermission('importadores.view')
|
||||
? [{
|
||||
name: 'Importadores',
|
||||
path: '/importers',
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M16 7a4 4 0 01-8 0M12 11v10m-6 0h12a2 2 0 002-2v-5a2 2 0 00-2-2H6a2 2 0 00-2 2v5a2 2 0 002 2z" />
|
||||
</svg>
|
||||
)
|
||||
}]
|
||||
: []
|
||||
),
|
||||
...(
|
||||
hasPermission('usuarios.view')
|
||||
? [{
|
||||
name: 'Usuarios',
|
||||
path: '/users',
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z" />
|
||||
</svg>
|
||||
)
|
||||
}]
|
||||
: []
|
||||
),
|
||||
...(
|
||||
hasPermission('usuarios.manage_roles')
|
||||
? [{
|
||||
name: 'Perfiles',
|
||||
path: '/profiles',
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||||
</svg>
|
||||
)
|
||||
}]
|
||||
: []
|
||||
),
|
||||
...(
|
||||
hasPermission('vucem.view')
|
||||
? [{
|
||||
name: 'Ventanilla Única',
|
||||
path: '/vucem',
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="2" fill="none" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 12h8M12 8v8" />
|
||||
</svg>
|
||||
)
|
||||
}]
|
||||
: []
|
||||
),
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// Filtrar secciones según si es importador y modo debug
|
||||
// Modificar items según si es importador
|
||||
const menuSections = allMenuSections
|
||||
.map(section => {
|
||||
if (section.title === 'Organización') {
|
||||
return {
|
||||
...section,
|
||||
items: section.items.filter(item => !(isImportador && item.name === 'Mi Organización'))
|
||||
};
|
||||
}
|
||||
// Para Tableros, filtrar la sección si es importador o si no está en modo debug
|
||||
if (section.title === 'Tableros' && (isImportador || !isDebugMode)) {
|
||||
return null;
|
||||
}
|
||||
return section;
|
||||
})
|
||||
.filter(Boolean);
|
||||
// Ocultar secciones que no tienen ningún item visible
|
||||
const menuSections = allMenuSections.filter(
|
||||
section => section && section.items && section.items.length > 0
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user