|
|
|
|
@@ -2,6 +2,7 @@
|
|
|
|
|
import { api } from '@sgse-app/backend/convex/_generated/api';
|
|
|
|
|
import { useConvexClient, useQuery } from 'convex-svelte';
|
|
|
|
|
import type { FunctionReference } from 'convex/server';
|
|
|
|
|
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
|
|
|
|
|
import {
|
|
|
|
|
Home,
|
|
|
|
|
User,
|
|
|
|
|
@@ -73,63 +74,63 @@
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Estrutura do menu definida no frontend
|
|
|
|
|
const MENU_STRUCTURE: MenuItem[] = [
|
|
|
|
|
const MENU_STRUCTURE = [
|
|
|
|
|
{
|
|
|
|
|
label: 'Dashboard',
|
|
|
|
|
icon: 'Home',
|
|
|
|
|
link: resolve('/')
|
|
|
|
|
link: '/'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: 'Gestão de Pessoas',
|
|
|
|
|
icon: 'Users',
|
|
|
|
|
link: resolve('/recursos-humanos'),
|
|
|
|
|
link: '/recursos-humanos',
|
|
|
|
|
permission: { recurso: 'gestao_pessoas', acao: 'ver' },
|
|
|
|
|
submenus: [
|
|
|
|
|
{
|
|
|
|
|
label: 'Funcionários',
|
|
|
|
|
link: resolve('/recursos-humanos/funcionarios'),
|
|
|
|
|
link: '/recursos-humanos/funcionarios',
|
|
|
|
|
permission: { recurso: 'funcionarios', acao: 'listar' },
|
|
|
|
|
exact: true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: 'Cadastro de Funcionários',
|
|
|
|
|
link: resolve('/recursos-humanos/funcionarios/cadastro'),
|
|
|
|
|
link: '/recursos-humanos/funcionarios/cadastro',
|
|
|
|
|
permission: { recurso: 'funcionarios', acao: 'criar' }
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: 'Exclusão de Funcionários',
|
|
|
|
|
link: resolve('/recursos-humanos/funcionarios/excluir'),
|
|
|
|
|
link: '/recursos-humanos/funcionarios/excluir',
|
|
|
|
|
permission: { recurso: 'funcionarios', acao: 'excluir' }
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: 'Férias',
|
|
|
|
|
link: resolve('/recursos-humanos/ferias'),
|
|
|
|
|
link: '/recursos-humanos/ferias',
|
|
|
|
|
permission: { recurso: 'ferias', acao: 'dashboard' }
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: 'Atestados de Licenças',
|
|
|
|
|
link: resolve('/recursos-humanos/atestados-licencas'),
|
|
|
|
|
link: '/recursos-humanos/atestados-licencas',
|
|
|
|
|
permission: { recurso: 'atestados_licencas', acao: 'listar' }
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: 'Controle de Ponto',
|
|
|
|
|
link: resolve('/recursos-humanos/controle-ponto'),
|
|
|
|
|
link: '/recursos-humanos/controle-ponto',
|
|
|
|
|
permission: { recurso: 'ponto', acao: 'ver' },
|
|
|
|
|
exact: true
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: 'Banco de Horas',
|
|
|
|
|
link: resolve('/recursos-humanos/controle-ponto/banco-horas'),
|
|
|
|
|
link: '/recursos-humanos/controle-ponto/banco-horas',
|
|
|
|
|
permission: { recurso: 'banco_horas', acao: 'ver' }
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: 'Registro de Ponto',
|
|
|
|
|
link: resolve('/recursos-humanos/registro-pontos'),
|
|
|
|
|
link: '/recursos-humanos/registro-pontos',
|
|
|
|
|
permission: { recurso: 'ponto', acao: 'ver' }
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: 'Símbolos',
|
|
|
|
|
link: resolve('/recursos-humanos/simbolos'),
|
|
|
|
|
link: '/recursos-humanos/simbolos',
|
|
|
|
|
permission: { recurso: 'simbolos', acao: 'listar' }
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
@@ -137,23 +138,23 @@
|
|
|
|
|
{
|
|
|
|
|
label: 'Pedidos',
|
|
|
|
|
icon: 'ClipboardCheck',
|
|
|
|
|
link: resolve('/pedidos'),
|
|
|
|
|
link: '/pedidos',
|
|
|
|
|
permission: { recurso: 'pedidos', acao: 'listar' },
|
|
|
|
|
submenus: [
|
|
|
|
|
{
|
|
|
|
|
label: 'Meus Pedidos',
|
|
|
|
|
link: resolve('/pedidos'),
|
|
|
|
|
link: '/pedidos',
|
|
|
|
|
permission: { recurso: 'pedidos', acao: 'listar' },
|
|
|
|
|
excludePaths: ['/pedidos/aceite', '/pedidos/minhas-analises']
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: 'Pedidos para Aceite',
|
|
|
|
|
link: resolve('/pedidos/aceite'),
|
|
|
|
|
link: '/pedidos/aceite',
|
|
|
|
|
permission: { recurso: 'pedidos', acao: 'aceitar' }
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: 'Minhas Análises',
|
|
|
|
|
link: resolve('/pedidos/minhas-analises'),
|
|
|
|
|
link: '/pedidos/minhas-analises',
|
|
|
|
|
permission: { recurso: 'pedidos', acao: 'aceitar' }
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
@@ -161,41 +162,41 @@
|
|
|
|
|
{
|
|
|
|
|
label: 'Objetos',
|
|
|
|
|
icon: 'Tag',
|
|
|
|
|
link: resolve('/compras/objetos'),
|
|
|
|
|
link: '/compras/objetos',
|
|
|
|
|
permission: { recurso: 'objetos', acao: 'listar' }
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: 'Atas de Registro',
|
|
|
|
|
icon: 'FileText',
|
|
|
|
|
link: resolve('/compras/atas'),
|
|
|
|
|
link: '/compras/atas',
|
|
|
|
|
permission: { recurso: 'atas', acao: 'listar' }
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: 'Contratos',
|
|
|
|
|
icon: 'FileText',
|
|
|
|
|
link: resolve('/licitacoes/contratos'),
|
|
|
|
|
link: '/licitacoes/contratos',
|
|
|
|
|
permission: { recurso: 'contratos', acao: 'listar' }
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: 'Empresas',
|
|
|
|
|
icon: 'Briefcase',
|
|
|
|
|
link: resolve('/licitacoes/empresas'),
|
|
|
|
|
link: '/licitacoes/empresas',
|
|
|
|
|
permission: { recurso: 'empresas', acao: 'listar' }
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: 'Fluxos & Processos',
|
|
|
|
|
icon: 'GitMerge',
|
|
|
|
|
link: resolve('/fluxos'),
|
|
|
|
|
link: '/fluxos',
|
|
|
|
|
permission: { recurso: 'fluxos_instancias', acao: 'listar' },
|
|
|
|
|
submenus: [
|
|
|
|
|
{
|
|
|
|
|
label: 'Meus Processos',
|
|
|
|
|
link: resolve('/fluxos/meus-processos'),
|
|
|
|
|
link: '/fluxos/meus-processos',
|
|
|
|
|
permission: { recurso: 'fluxos_instancias', acao: 'listar' }
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: 'Modelos de Fluxo',
|
|
|
|
|
link: resolve('/fluxos/templates'),
|
|
|
|
|
link: '/fluxos/templates',
|
|
|
|
|
permission: { recurso: 'fluxos_templates', acao: 'listar' }
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
@@ -203,10 +204,10 @@
|
|
|
|
|
{
|
|
|
|
|
label: 'Painel de TI',
|
|
|
|
|
icon: 'Settings',
|
|
|
|
|
link: resolve('/ti'),
|
|
|
|
|
link: '/ti',
|
|
|
|
|
permission: { recurso: 'ti_painel_administrativo', acao: 'ver' }
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
] as const satisfies readonly MenuItem[];
|
|
|
|
|
|
|
|
|
|
type IconType = typeof Home;
|
|
|
|
|
|
|
|
|
|
@@ -224,6 +225,20 @@
|
|
|
|
|
const permissionsQuery = useQuery(api.menu.getUserPermissions as FunctionReference<'query'>, {});
|
|
|
|
|
|
|
|
|
|
// Filtrar menu baseado nas permissões do usuário
|
|
|
|
|
function filterSubmenusByPermissions(
|
|
|
|
|
items: SubMenuItem[],
|
|
|
|
|
isMaster: boolean,
|
|
|
|
|
permissionsSet: Set<string>
|
|
|
|
|
): SubMenuItem[] {
|
|
|
|
|
if (isMaster) return items;
|
|
|
|
|
|
|
|
|
|
return items.filter((item) => {
|
|
|
|
|
if (!item.permission) return true;
|
|
|
|
|
const key = `${item.permission.recurso}.${item.permission.acao}`;
|
|
|
|
|
return permissionsSet.has(key);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function filterMenuByPermissions(
|
|
|
|
|
items: MenuItem[],
|
|
|
|
|
isMaster: boolean,
|
|
|
|
|
@@ -231,30 +246,32 @@
|
|
|
|
|
): MenuItem[] {
|
|
|
|
|
if (isMaster) return items;
|
|
|
|
|
|
|
|
|
|
return items
|
|
|
|
|
.map((item) => {
|
|
|
|
|
// Verifica permissão do item atual
|
|
|
|
|
let hasPermission = true;
|
|
|
|
|
if (item.permission) {
|
|
|
|
|
const key = `${item.permission.recurso}.${item.permission.acao}`;
|
|
|
|
|
hasPermission = permissionsSet.has(key);
|
|
|
|
|
}
|
|
|
|
|
const filtered: MenuItem[] = [];
|
|
|
|
|
|
|
|
|
|
// Se não tem permissão, não mostra
|
|
|
|
|
if (!hasPermission) return null;
|
|
|
|
|
for (const item of items) {
|
|
|
|
|
// Verifica permissão do item atual
|
|
|
|
|
let hasPermission = true;
|
|
|
|
|
if (item.permission) {
|
|
|
|
|
const key = `${item.permission.recurso}.${item.permission.acao}`;
|
|
|
|
|
hasPermission = permissionsSet.has(key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Se tiver submenus, filtra eles recursivamente
|
|
|
|
|
let filteredSubmenus: MenuItem[] | undefined = undefined;
|
|
|
|
|
if (item.submenus) {
|
|
|
|
|
filteredSubmenus = filterMenuByPermissions(item.submenus, isMaster, permissionsSet);
|
|
|
|
|
}
|
|
|
|
|
if (!hasPermission) continue;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...item,
|
|
|
|
|
submenus: filteredSubmenus && filteredSubmenus.length > 0 ? filteredSubmenus : undefined
|
|
|
|
|
};
|
|
|
|
|
})
|
|
|
|
|
.filter((item): item is MenuItem => item !== null);
|
|
|
|
|
// Se tiver submenus, filtra e só mantém se sobrar algo
|
|
|
|
|
let filteredSubmenus: SubMenuItem[] | undefined = undefined;
|
|
|
|
|
if (item.submenus) {
|
|
|
|
|
const subs = filterSubmenusByPermissions(item.submenus, isMaster, permissionsSet);
|
|
|
|
|
filteredSubmenus = subs.length > 0 ? subs : undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
filtered.push({
|
|
|
|
|
...item,
|
|
|
|
|
submenus: filteredSubmenus
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return filtered;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Menu filtrado reativo
|
|
|
|
|
@@ -262,7 +279,7 @@
|
|
|
|
|
const data = permissionsQuery.data;
|
|
|
|
|
if (!data) return [];
|
|
|
|
|
|
|
|
|
|
const permissionsSet = new Set(data.permissions);
|
|
|
|
|
const permissionsSet = new Set((data.permissions ?? []) as string[]);
|
|
|
|
|
return filterMenuByPermissions(MENU_STRUCTURE, data.isMaster, permissionsSet);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
@@ -470,7 +487,11 @@
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Buscar o usuário no Convex usando getCurrentUser
|
|
|
|
|
const usuario = await convexClient.query(api.auth.getCurrentUser, {});
|
|
|
|
|
// (o typesafe FunctionReference pode falhar aqui; tipamos o retorno e mantemos a chamada)
|
|
|
|
|
const usuario = (await convexClient.query(
|
|
|
|
|
api.auth.getCurrentUser as unknown as FunctionReference<'query'>,
|
|
|
|
|
{}
|
|
|
|
|
)) as { _id?: Id<'usuarios'> } | null;
|
|
|
|
|
|
|
|
|
|
if (usuario && usuario._id) {
|
|
|
|
|
await convexClient.mutation(api.logsLogin.registrarTentativaLogin, {
|
|
|
|
|
|