feat: roles con modulos por rol + mejoras Mi dia y descarte
- Matriz de roles (vendedor, administracion_venta, director_ventas, escrituracion, direccion_general, admin) con modulos visibles por rol; auth/me y login incluyen 'modulos'; PATCH /usuarios/:id/rol (admin). - Landing: Cobranza -> Clientes, Chat IA -> Chat Hermes, nuevo modulo Contactos (vista interna leyendo el tablero Contactos), grid filtrado por rol, rol visible en Configuracion de cuenta. - Dashboards: tab 'Tablero comercial' y tabs filtrados por rol. - Boton '<- Menu' en el header de todas las apps. - Mi dia: cada lead muestra el ultimo cambio (que, quien, cuando) desde el activity log de Monday (normalizacion de created_at 100ns/us/ms). - Descarte: el motivo se crea en el grupo de su categoria en el tablero Motivos No Compra.
This commit is contained in:
@@ -1,9 +1,50 @@
|
||||
import { MODULOS } from './data/modulos'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { MODULOS, esModuloVisible } from './data/modulos'
|
||||
import { Navbar } from './components/Navbar'
|
||||
import { TarjetaModulo } from './components/TarjetaModulo'
|
||||
import { ConfiguracionCuenta } from './components/ConfiguracionCuenta'
|
||||
import { VistaContactos } from './components/VistaContactos'
|
||||
import { getToken } from './services/auth'
|
||||
import { getMe } from './services/api'
|
||||
|
||||
type Vista = 'home' | 'contactos'
|
||||
|
||||
export default function App() {
|
||||
const [vista, setVista] = useState<Vista>('home')
|
||||
// Módulos permitidos por el rol (GET /auth/me); null = sin sesión → se muestran todos
|
||||
const [permitidos, setPermitidos] = useState<string[] | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
let cancelado = false
|
||||
const cargar = () => {
|
||||
if (!getToken()) {
|
||||
setPermitidos(null)
|
||||
return
|
||||
}
|
||||
// Si /me falla se conserva lo anterior (o todos): el login wall de cada app sigue
|
||||
getMe()
|
||||
.then((usuario) => {
|
||||
if (!cancelado) setPermitidos(usuario.modulos ?? [])
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
cargar()
|
||||
window.addEventListener('frasa:sesion', cargar)
|
||||
return () => {
|
||||
cancelado = true
|
||||
window.removeEventListener('frasa:sesion', cargar)
|
||||
}
|
||||
}, [])
|
||||
|
||||
if (vista === 'contactos') {
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col">
|
||||
<Navbar />
|
||||
<VistaContactos onVolver={() => setVista('home')} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col">
|
||||
<Navbar />
|
||||
@@ -24,8 +65,12 @@ export default function App() {
|
||||
|
||||
<nav aria-label="Módulos del ecosistema">
|
||||
<ul className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{MODULOS.map((modulo) => (
|
||||
<TarjetaModulo key={modulo.id} modulo={modulo} />
|
||||
{MODULOS.filter((modulo) => esModuloVisible(modulo, permitidos)).map((modulo) => (
|
||||
<TarjetaModulo
|
||||
key={modulo.id}
|
||||
modulo={modulo}
|
||||
onAbrir={modulo.id === 'contactos' ? () => setVista('contactos') : undefined}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
Reference in New Issue
Block a user