diff --git a/apps/web/src/lib/components/Sidebar.svelte b/apps/web/src/lib/components/Sidebar.svelte index 98532a3..3c40c08 100644 --- a/apps/web/src/lib/components/Sidebar.svelte +++ b/apps/web/src/lib/components/Sidebar.svelte @@ -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 + ): 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, { diff --git a/bun.lock b/bun.lock index 9c6d4fd..9f55aa7 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "sgse-app", diff --git a/packages/backend/convex/ausencias.ts b/packages/backend/convex/ausencias.ts index 819f7e4..2155289 100644 --- a/packages/backend/convex/ausencias.ts +++ b/packages/backend/convex/ausencias.ts @@ -72,7 +72,7 @@ export const listarMinhasSolicitacoes = query({ handler: async (ctx, args) => { await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { recurso: 'ausencias', - acao: 'criar' + acao: 'listar' }); const solicitacoes = await ctx.db @@ -259,7 +259,7 @@ export const obterNotificacoesNaoLidas = query({ handler: async (ctx, args) => { await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { recurso: 'ausencias', - acao: 'criar' + acao: 'listar' }); const notificacoes = await ctx.db @@ -913,7 +913,7 @@ export const marcarComoLida = mutation({ handler: async (ctx, args) => { await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { recurso: 'ausencias', - acao: 'criar' + acao: 'listar' }); await ctx.db.patch(args.notificacaoId, { diff --git a/packages/backend/convex/pontos.ts b/packages/backend/convex/pontos.ts index f626205..889342e 100644 --- a/packages/backend/convex/pontos.ts +++ b/packages/backend/convex/pontos.ts @@ -2782,8 +2782,8 @@ export const obterMotivosAtestados = query({ args: {}, handler: async (ctx) => { await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { - recurso: 'banco_horas', - acao: 'ajustar' + recurso: 'ponto', + acao: 'editar' }); // Buscar tipos de atestados e declarações @@ -4364,7 +4364,7 @@ export const detectarEEnviarAlertasBancoHoras = internalMutation({ } break; - case 'dias_sem_registro': + case 'dias_sem_registro': { // Verificar últimos 7 dias const ultimos7Dias: string[] = []; for (let i = 0; i < 7; i++) { @@ -4391,6 +4391,7 @@ export const detectarEEnviarAlertasBancoHoras = internalMutation({ mensagem = `O funcionário não possui registro de ponto em ${diasSemRegistro.length} dos últimos 7 dias.`; } break; + } case 'limite_saldo_excedido': if (bancoMensal) {