Feat pedidos #63

Merged
killer-cf merged 4 commits from feat-pedidos into master 2025-12-12 13:22:53 +00:00
10 changed files with 757 additions and 94 deletions
Showing only changes of commit ba39167b2b - Show all commits

View File

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

View File

@@ -1,5 +1,6 @@
{ {
"lockfileVersion": 1, "lockfileVersion": 1,
"configVersion": 0,
"workspaces": { "workspaces": {
"": { "": {
"name": "sgse-app", "name": "sgse-app",

View File

@@ -72,7 +72,7 @@ export const listarMinhasSolicitacoes = query({
handler: async (ctx, args) => { handler: async (ctx, args) => {
await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, {
recurso: 'ausencias', recurso: 'ausencias',
acao: 'criar' acao: 'listar'
}); });
const solicitacoes = await ctx.db const solicitacoes = await ctx.db
@@ -259,7 +259,7 @@ export const obterNotificacoesNaoLidas = query({
handler: async (ctx, args) => { handler: async (ctx, args) => {
await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, {
recurso: 'ausencias', recurso: 'ausencias',
acao: 'criar' acao: 'listar'
}); });
const notificacoes = await ctx.db const notificacoes = await ctx.db
@@ -913,7 +913,7 @@ export const marcarComoLida = mutation({
handler: async (ctx, args) => { handler: async (ctx, args) => {
await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, {
recurso: 'ausencias', recurso: 'ausencias',
acao: 'criar' acao: 'listar'
}); });
await ctx.db.patch(args.notificacaoId, { await ctx.db.patch(args.notificacaoId, {

View File

@@ -2782,8 +2782,8 @@ export const obterMotivosAtestados = query({
args: {}, args: {},
handler: async (ctx) => { handler: async (ctx) => {
await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, {
recurso: 'banco_horas', recurso: 'ponto',
acao: 'ajustar' acao: 'editar'
}); });
// Buscar tipos de atestados e declarações // Buscar tipos de atestados e declarações
@@ -4364,7 +4364,7 @@ export const detectarEEnviarAlertasBancoHoras = internalMutation({
} }
break; break;
case 'dias_sem_registro': case 'dias_sem_registro': {
// Verificar últimos 7 dias // Verificar últimos 7 dias
const ultimos7Dias: string[] = []; const ultimos7Dias: string[] = [];
for (let i = 0; i < 7; i++) { 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.`; mensagem = `O funcionário não possui registro de ponto em ${diasSemRegistro.length} dos últimos 7 dias.`;
} }
break; break;
}
case 'limite_saldo_excedido': case 'limite_saldo_excedido':
if (bancoMensal) { if (bancoMensal) {