diff --git a/src/components/users/matriz/MatrizAcessosPainel.tsx b/src/components/users/matriz/MatrizAcessosPainel.tsx new file mode 100644 index 0000000..ca476e5 --- /dev/null +++ b/src/components/users/matriz/MatrizAcessosPainel.tsx @@ -0,0 +1,153 @@ +import React, { useState } from 'react'; +import { useMatrizAcessos, UsuarioMatriz } from '@/hooks/useMatrizAcessos'; +import { MatrizGrid } from './MatrizGrid'; +import { MatrizTemplatesModal } from './MatrizTemplatesModal'; +import { SimuladorVisaoBar } from './SimuladorVisaoBar'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Search, RefreshCw, Users, Shield, Factory, Sparkles } from 'lucide-react'; + +interface MatrizAcessosPainelProps { + onOpenNovoUsuario?: () => void; +} + +export const MatrizAcessosPainel: React.FC = ({ + onOpenNovoUsuario, +}) => { + const { + loading, + usuarios, + gruposModulos, + usuarioSimuladoId, + setUsuarioSimuladoId, + togglePermissao, + aplicarTemplate, + recarregar, + } = useMatrizAcessos(); + + const [busca, setBusca] = useState(''); + const [selectedUserForTemplate, setSelectedUserForTemplate] = useState(null); + + // Filtragem por nome, email ou cargo + const usuariosFiltrados = usuarios.filter((u) => { + const termo = busca.toLowerCase(); + return ( + u.fullName.toLowerCase().includes(termo) || + u.email.toLowerCase().includes(termo) || + u.functionName.toLowerCase().includes(termo) + ); + }); + + // Métricas rápidas + const totalUsuarios = usuarios.length; + const totalSmartExclusivo = usuarios.filter( + (u) => u.recursosPermitidos.has('modo-smart') && u.recursosPermitidos.size <= 4 + ).length; + const totalAdmins = usuarios.filter((u) => + u.privilegeName.toLowerCase().includes('admin') + ).length; + + return ( +
+ {/* 1. CARDS DE RESUMO RÁPIDO DO ACESSO */} +
+
+
+ Total de Usuários +

{totalUsuarios}

+
+
+ +
+
+ +
+
+ Operadores Modo Smart +

{totalSmartExclusivo}

+
+
+ +
+
+ +
+
+ Administradores +

{totalAdmins}

+
+
+ +
+
+
+ + {/* 2. SIMULADOR DE VISÃO */} + + + {/* 3. BARRA DE CONTROLE E BUSCA */} +
+
+ + setBusca(e.target.value)} + className="pl-8 text-xs h-9 bg-background" + /> +
+ +
+ + + {onOpenNovoUsuario && ( + + )} +
+
+ + {/* 4. A GRADE INTERATIVA (MATRIZ) */} + {loading ? ( +
+ Carregando matriz de permissões e usuários... +
+ ) : ( + setSelectedUserForTemplate(user)} + /> + )} + + {/* 5. MODAL DE TEMPLATES RÁPIDOS */} + setSelectedUserForTemplate(null)} + onSelectTemplate={aplicarTemplate} + /> +
+ ); +}; diff --git a/src/components/users/matriz/MatrizGrid.tsx b/src/components/users/matriz/MatrizGrid.tsx new file mode 100644 index 0000000..d041d19 --- /dev/null +++ b/src/components/users/matriz/MatrizGrid.tsx @@ -0,0 +1,179 @@ +import React from 'react'; +import { UsuarioMatriz, GrupoRecurso } from '@/hooks/useMatrizAcessos'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { Check, X, Shield, Sparkles, User, HelpCircle } from 'lucide-react'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; + +interface MatrizGridProps { + usuarios: UsuarioMatriz[]; + grupos: GrupoRecurso[]; + onTogglePermissao: (userId: string, resourceKey: string) => void; + onOpenTemplates: (user: UsuarioMatriz) => void; +} + +export const MatrizGrid: React.FC = ({ + usuarios, + grupos, + onTogglePermissao, + onOpenTemplates, +}) => { + // Cores de fundo e borda para os grupos + const corGrupoHeader: Record = { + emerald: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/30', + sky: 'bg-sky-500/10 text-sky-400 border-sky-500/30', + indigo: 'bg-indigo-500/10 text-indigo-400 border-indigo-500/30', + amber: 'bg-amber-500/10 text-amber-400 border-amber-500/30', + teal: 'bg-teal-500/10 text-teal-400 border-teal-500/30', + rose: 'bg-rose-500/10 text-rose-400 border-rose-500/30', + }; + + return ( +
+
+ + {/* CABEÇALHO DE GRUPOS DE MÓDULOS */} + + + + + {grupos.map((grupo) => ( + + ))} + + + {/* SUB-CABEÇALHO: CADA TELA/RECURSO ESPECÍFICO */} + + + + {grupos.map((grupo) => + grupo.recursos.map((rec) => ( + + )) + )} + + + + {/* LINHAS: UM USUÁRIO POR LINHA */} + + {usuarios.length === 0 ? ( + + + + ) : ( + usuarios.map((user) => ( + + {/* Coluna Fixa do Usuário */} + + + {/* Células Clicáveis da Matriz */} + {grupos.map((grupo) => + grupo.recursos.map((rec) => { + const temAcesso = user.recursosPermitidos.has(rec.key); + + return ( + + ); + }) + )} + + )) + )} + +
+ + Usuário / Colaborador + + +
+ {grupo.titulo} +
+ + {grupo.descricao} + +
+ Perfil & Templates + + + {rec.nome} + +
+ Nenhum usuário encontrado. +
+
+
+ + + + {user.fullName.substring(0, 2).toUpperCase()} + + +
+

+ {user.fullName} +

+

+ {user.functionName} +

+
+
+ + {/* Botão de Template em 1 Clique */} + +
+
+ + + + + + +

{rec.nome}

+

+ {temAcesso ? 'Clique para Bloquear' : 'Clique para Liberar'} +

+
+
+
+
+
+
+ ); +}; diff --git a/src/components/users/matriz/MatrizTemplatesModal.tsx b/src/components/users/matriz/MatrizTemplatesModal.tsx new file mode 100644 index 0000000..a666000 --- /dev/null +++ b/src/components/users/matriz/MatrizTemplatesModal.tsx @@ -0,0 +1,120 @@ +import React from 'react'; +import { UsuarioMatriz, TemplateAcesso, TEMPLATES_ACESSO } from '@/hooks/useMatrizAcessos'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, +} from '@/components/ui/dialog'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { + Factory, + Layers, + Truck, + HardHat, + Crown, + Sparkles, + CheckCircle2, +} from 'lucide-react'; + +interface MatrizTemplatesModalProps { + user: UsuarioMatriz | null; + isOpen: boolean; + onClose: () => void; + onSelectTemplate: (userId: string, templateId: string) => void; +} + +export const MatrizTemplatesModal: React.FC = ({ + user, + isOpen, + onClose, + onSelectTemplate, +}) => { + if (!user) return null; + + const getIcone = (icone: string) => { + switch (icone) { + case 'Factory': + return Factory; + case 'Layers': + return Layers; + case 'Truck': + return Truck; + case 'HardHat': + return HardHat; + case 'Crown': + return Crown; + default: + return Sparkles; + } + }; + + return ( + !open && onClose()}> + + +
+
+ +
+
+ + Templates de Acesso em 1 Clique + + + Selecione um perfil pronto para aplicar instantaneamente a{' '} + {user.fullName}. + +
+
+
+ +
+ {TEMPLATES_ACESSO.map((template) => { + const Icon = getIcone(template.icone); + + return ( +
{ + onSelectTemplate(user.id, template.id); + onClose(); + }} + className="group relative rounded-xl border border-border/80 bg-card hover:border-primary/50 hover:bg-accent/40 p-3.5 transition-all cursor-pointer flex items-center justify-between gap-4" + > +
+
+ +
+
+
+

+ {template.nome} +

+ + {template.badge} + +
+

+ {template.descricao} +

+
+
+ + +
+ ); + })} +
+
+
+ ); +}; diff --git a/src/components/users/matriz/SimuladorVisaoBar.tsx b/src/components/users/matriz/SimuladorVisaoBar.tsx new file mode 100644 index 0000000..d383526 --- /dev/null +++ b/src/components/users/matriz/SimuladorVisaoBar.tsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { UsuarioMatriz, GrupoRecurso } from '@/hooks/useMatrizAcessos'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Eye, EyeOff, ShieldCheck, CheckCircle2, XCircle, RotateCcw } from 'lucide-react'; + +interface SimuladorVisaoBarProps { + usuarios: UsuarioMatriz[]; + grupos: GrupoRecurso[]; + usuarioSimuladoId: string | null; + onSelectUsuarioSimulado: (id: string | null) => void; +} + +export const SimuladorVisaoBar: React.FC = ({ + usuarios, + grupos, + usuarioSimuladoId, + onSelectUsuarioSimulado, +}) => { + const usuarioSimulado = usuarios.find((u) => u.id === usuarioSimuladoId); + + // Total de módulos disponíveis + const totalModulos = grupos.reduce((acc, g) => acc + g.recursos.length, 0); + const totalPermitidos = usuarioSimulado ? usuarioSimulado.recursosPermitidos.size : 0; + + return ( +
+
+
+ +
+
+ + Simulador de Visão do Usuário + + + Selecione um usuário para auditar instantaneamente o que ele enxerga no sistema. + +
+
+ +
+ + + {usuarioSimulado && ( + <> + + + {usuarioSimulado.recursosPermitidos.has('modo-smart') && totalPermitidos <= 4 + ? 'Modo Smart Exclusivo' + : `${totalPermitidos}/${totalModulos} Módulos`} + + + + + )} +
+
+ ); +}; diff --git a/src/hooks/useMatrizAcessos.ts b/src/hooks/useMatrizAcessos.ts new file mode 100644 index 0000000..8b6ca18 --- /dev/null +++ b/src/hooks/useMatrizAcessos.ts @@ -0,0 +1,378 @@ +import { useState, useEffect, useMemo } from 'react'; +import { supabase } from '@/integrations/supabase/client'; +import { toast } from 'sonner'; + +export interface UsuarioMatriz { + id: string; + email: string; + fullName: string; + status: 'active' | 'pending' | 'inactive' | 'rejected'; + privilegeId: string | null; + privilegeName: string; + functionId: string | null; + functionName: string; + profileImageUrl: string | null; + recursosPermitidos: Set; +} + +export interface GrupoRecurso { + key: string; + titulo: string; + cor: string; // Ex: 'emerald', 'sky', 'indigo', 'amber', 'rose' + descricao: string; + recursos: { + key: string; + nome: string; + rota?: string; + }[]; +} + +// 6 Grupos Industriais Claros e Intuitivos +export const GRUPOS_MODULOS: GrupoRecurso[] = [ + { + key: 'smart', + titulo: 'Modo Smart (Chão de Fábrica)', + cor: 'emerald', + descricao: 'Interface touch para operadores e líderes de linha', + recursos: [ + { key: 'modo-smart', nome: 'Acesso Geral Modo Smart', rota: '/smart' }, + { key: 'smart-apontamento', nome: '1. Apontar Produção', rota: '/smart' }, + { key: 'smart-metricas', nome: '2. Ver Minhas Métricas', rota: '/smart' }, + { key: 'smart-pdf', nome: '3. Gerar Relatórios e PDFs', rota: '/smart' }, + ], + }, + { + key: 'producao_pcp', + titulo: 'Produção & PCP', + cor: 'sky', + descricao: 'Gestão de capacidade, apontamentos e mesa tática', + recursos: [ + { key: 'producao-pcp', nome: 'Mesa Tática PCP', rota: '/pcp' }, + { key: 'producao-dashboard', nome: 'Dashboard de Produção', rota: '/dashboard-producao' }, + { key: 'producao-apontamento', nome: 'Apontamento Administrativo', rota: '/apontamento-producao' }, + { key: 'diario-producao', nome: 'Diário de Produção', rota: '/diario-producao' }, + { key: 'prioridades-fabricacao', nome: 'Prioridades de Fabricação', rota: '/prioridades-fabricacao' }, + ], + }, + { + key: 'engenharia_3d', + titulo: 'Engenharia, OFs & BIM', + cor: 'indigo', + descricao: 'Projetos, modelos 3D, fichas técnicas e cronogramas', + recursos: [ + { key: 'visualizador-3d', nome: 'Visualizador 3D BIM', rota: '/visualizador-3d' }, + { key: 'ofs-lista', nome: 'Ordens de Fabricação', rota: '/ofs' }, + { key: 'ofs-cronograma', nome: 'Cronogramas de Fabricação', rota: '/ofs/cronograma' }, + { key: 'cadastro-pecas', nome: 'Cadastro de Peças & Conjuntos', rota: '/cadastro/pecas' }, + ], + }, + { + key: 'expedicao_logistica', + titulo: 'Expedição & Romaneios', + cor: 'amber', + descricao: 'Embarque, pesagem, transporte e romaneios', + recursos: [ + { key: 'expedicao', nome: 'Painel de Expedição', rota: '/expedicao' }, + { key: 'estoque', nome: 'Controle de Estoque', rota: '/estoque' }, + { key: 'estoque-solicitacao-compras', nome: 'Solicitação de Compras', rota: '/estoque/solicitacao-compras' }, + ], + }, + { + key: 'obra_montagem', + titulo: 'Obra & Montagem Externa', + cor: 'teal', + descricao: 'Acompanhamento do canteiro e montagem', + recursos: [ + { key: 'obra-dashboard', nome: 'Dashboard da Obra', rota: '/obra' }, + { key: 'obra-configuracoes', nome: 'Configurações de Obra', rota: '/obra/configuracoes' }, + ], + }, + { + key: 'admin_sistema', + titulo: 'Administração & Sistema', + cor: 'rose', + descricao: 'Controle de acessos, usuários e configurações', + recursos: [ + { key: 'user-management', nome: 'Matriz de Usuários & Acessos', rota: '/user-management' }, + { key: 'configuracoes-gerais', nome: 'Configurações Gerais', rota: '/configuracoes' }, + { key: 'ferramentas-inconsistencias', nome: 'Auditoria de Inconsistências', rota: '/ferramentas/inconsistencias' }, + { key: 'sistema', nome: 'Painel do Sistema', rota: '/sistema' }, + ], + }, +]; + +export interface TemplateAcesso { + id: string; + nome: string; + descricao: string; + icone: string; + badge: string; + recursosPermitidos: string[]; +} + +export const TEMPLATES_ACESSO: TemplateAcesso[] = [ + { + id: 'modo_smart_exclusivo', + nome: 'Operador Chão de Fábrica (Modo Smart)', + descricao: 'Acesso 100% restrito ao Modo Smart touch. Não enxerga nenhum menu corporativo do ERP.', + icone: 'Factory', + badge: 'Chão de Fábrica', + recursosPermitidos: [ + 'modo-smart', + 'smart-apontamento', + 'smart-metricas', + 'smart-pdf', + ], + }, + { + id: 'pcp_planejamento', + nome: 'PCP & Planejamento Industrial', + descricao: 'Acesso completo à Mesa Tática do PCP, Dashboards, Cronogramas, OFs e Visualizador 3D.', + icone: 'Layers', + badge: 'Planejamento', + recursosPermitidos: [ + 'producao-pcp', + 'producao-dashboard', + 'diario-producao', + 'prioridades-fabricacao', + 'visualizador-3d', + 'ofs-lista', + 'ofs-cronograma', + 'cadastro-pecas', + 'modo-smart', + ], + }, + { + id: 'expedicao_logistica', + nome: 'Expedição & Almoxarifado', + descricao: 'Controle de romaneios, pesagens, expedição de peças e solicitações de compras.', + icone: 'Truck', + badge: 'Logística', + recursosPermitidos: [ + 'expedicao', + 'estoque', + 'estoque-solicitacao-compras', + 'modo-smart', + ], + }, + { + id: 'fiscal_obra', + nome: 'Fiscal de Obra & Montagem', + descricao: 'Acompanhamento do canteiro de obras, romaneios recebidos e Modo Smart de montagem.', + icone: 'HardHat', + badge: 'Obra', + recursosPermitidos: [ + 'obra-dashboard', + 'obra-configuracoes', + 'visualizador-3d', + 'modo-smart', + ], + }, + { + id: 'administrador_total', + nome: 'Administrador / Gestor Total', + descricao: 'Acesso irrestrito a todas as telas, configurações, usuários e ferramentas do sistema.', + icone: 'Crown', + badge: 'Controle Total', + recursosPermitidos: GRUPOS_MODULOS.flatMap((g) => g.recursos.map((r) => r.key)), + }, +]; + +export const useMatrizAcessos = () => { + const [loading, setLoading] = useState(true); + const [usuarios, setUsuarios] = useState([]); + const [usuarioSimuladoId, setUsuarioSimuladoId] = useState(null); + + const carregarDados = async () => { + try { + setLoading(true); + + // 1. Carregar profiles com functions e privileges + const { data: profiles, error: profError } = await supabase + .from('profiles') + .select(` + id, + email, + full_name, + status, + privilege_id, + function_id, + profile_image_url, + privileges:privilege_id ( + id, + name + ), + user_functions:function_id ( + id, + name + ) + `) + .order('full_name', { ascending: true }); + + if (profError) throw profError; + + // 2. Carregar recursos por privilégio + const { data: privResources, error: privResError } = await supabase + .from('privilege_interface_resources') + .select('privilege_id, resource_key'); + + if (privResError) console.warn('Aviso ao carregar privilege_interface_resources:', privResError); + + // Indexar recursos por privilege_id + const recursosPorPrivilege: Record> = {}; + (privResources || []).forEach((pr: any) => { + if (!recursosPorPrivilege[pr.privilege_id]) { + recursosPorPrivilege[pr.privilege_id] = new Set(); + } + recursosPorPrivilege[pr.privilege_id].add(pr.resource_key); + }); + + // Mapear usuários para a Matriz + const mapped: UsuarioMatriz[] = (profiles || []).map((p: any) => { + const privId = p.privilege_id; + const recursos = privId && recursosPorPrivilege[privId] + ? new Set(recursosPorPrivilege[privId]) + : new Set(); + + // Se o privilégio tiver nome "Admin" ou similar, já inclui todos + const privNome = p.privileges?.name || 'Padrão'; + if (privNome.toLowerCase().includes('admin')) { + GRUPOS_MODULOS.forEach((g) => g.recursos.forEach((r) => recursos.add(r.key))); + } + + return { + id: p.id, + email: p.email || '', + fullName: p.full_name || p.email?.split('@')[0] || 'Usuário', + status: p.status || 'active', + privilegeId: privId, + privilegeName: privNome, + functionId: p.function_id, + functionName: p.user_functions?.name || 'Sem Cargo', + profileImageUrl: p.profile_image_url, + recursosPermitidos: recursos, + }; + }); + + setUsuarios(mapped); + } catch (err: any) { + console.error('Erro ao carregar dados da matriz de acessos:', err); + toast.error('Erro ao carregar dados de usuários e permissões.'); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + carregarDados(); + }, []); + + // TOGGLE DIRETO DE UMA PERMISSÃO NA MATRIZ + const togglePermissao = async (userId: string, resourceKey: string) => { + const user = usuarios.find((u) => u.id === userId); + if (!user) return; + + const temAcesso = user.recursosPermitidos.has(resourceKey); + const novoSet = new Set(user.recursosPermitidos); + + if (temAcesso) { + novoSet.delete(resourceKey); + } else { + novoSet.add(resourceKey); + } + + // Atualização otimista na tela + setUsuarios((prev) => + prev.map((u) => (u.id === userId ? { ...u, recursosPermitidos: novoSet } : u)) + ); + + try { + // Se o usuário tem privilege_id, salvar no banco + if (user.privilegeId) { + if (temAcesso) { + // Remover + await supabase + .from('privilege_interface_resources') + .delete() + .eq('privilege_id', user.privilegeId) + .eq('resource_key', resourceKey); + } else { + // Adicionar + await supabase + .from('privilege_interface_resources') + .upsert( + { privilege_id: user.privilegeId, resource_key: resourceKey }, + { onConflict: 'privilege_id,resource_key' } + ); + } + } + toast.success( + temAcesso + ? `Módulo '${resourceKey}' bloqueado para ${user.fullName}.` + : `Módulo '${resourceKey}' liberado para ${user.fullName}!` + ); + } catch (err) { + console.error('Erro ao atualizar permissão:', err); + toast.error('Erro ao salvar alteração no banco.'); + carregarDados(); // Reverter se falhar + } + }; + + // APLICAR TEMPLATE PRÉ-CONFIGURADO EM 1 CLIQUE + const aplicarTemplate = async (userId: string, templateId: string) => { + const user = usuarios.find((u) => u.id === userId); + const template = TEMPLATES_ACESSO.find((t) => t.id === templateId); + if (!user || !template) return; + + const novoSet = new Set(template.recursosPermitidos); + + // Atualização otimista + setUsuarios((prev) => + prev.map((u) => (u.id === userId ? { ...u, recursosPermitidos: novoSet } : u)) + ); + + try { + if (user.privilegeId) { + // Limpar recursos atuais do privilégio + await supabase + .from('privilege_interface_resources') + .delete() + .eq('privilege_id', user.privilegeId); + + // Inserir os novos recursos do template + const records = template.recursosPermitidos.map((resKey) => ({ + privilege_id: user.privilegeId, + resource_key: resKey, + })); + + if (records.length > 0) { + await supabase.from('privilege_interface_resources').insert(records); + } + } + + toast.success(`Template '${template.nome}' aplicado com sucesso a ${user.fullName}!`); + } catch (err) { + console.error('Erro ao aplicar template:', err); + toast.error('Erro ao salvar template.'); + carregarDados(); + } + }; + + // Usuário sendo simulado atualmente (para o visualizador "Ver como este usuário") + const usuarioSimulado = useMemo(() => { + return usuarios.find((u) => u.id === usuarioSimuladoId) || null; + }, [usuarios, usuarioSimuladoId]); + + return { + loading, + usuarios, + gruposModulos: GRUPOS_MODULOS, + templates: TEMPLATES_ACESSO, + usuarioSimulado, + usuarioSimuladoId, + setUsuarioSimuladoId, + togglePermissao, + aplicarTemplate, + recarregar: carregarDados, + }; +}; diff --git a/src/pages/Auth.tsx b/src/pages/Auth.tsx index 7591ab2..809ff73 100644 --- a/src/pages/Auth.tsx +++ b/src/pages/Auth.tsx @@ -16,7 +16,7 @@ import { Eye, EyeOff, Mail, Lock } from 'lucide-react'; const Auth = () => { const [isLoading, setIsLoading] = useState(false); - const { signIn, user, loading, isRecoveryFlow, session } = useAuth(); + const { signIn, user, loading, isRecoveryFlow } = useAuth(); const { brandSettings } = useBrandSettings(); const navigate = useNavigate(); const [searchParams] = useSearchParams(); @@ -46,11 +46,16 @@ const Auth = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [shouldShowReset, showPasswordReset, loading, location.pathname]); - // Redirecionar usuários autenticados para página principal (exceto em fluxo de recuperação) + // Redirecionar usuários autenticados para página principal ou Modo Smart useEffect(() => { if (!loading && user && !showPasswordReset) { - console.log('🔄 Redirecionando usuário autenticado para página principal'); - navigate('/'); + const isSmartMode = localStorage.getItem('tracksteel_app_mode') === 'smart'; + console.log('🔄 Redirecionando usuário autenticado:', isSmartMode ? '/smart' : '/'); + if (isSmartMode) { + navigate('/smart'); + } else { + navigate('/'); + } } }, [user, loading, navigate, showPasswordReset]); diff --git a/src/pages/UserManagement.tsx b/src/pages/UserManagement.tsx index 3e59d59..753b840 100644 --- a/src/pages/UserManagement.tsx +++ b/src/pages/UserManagement.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; -import { Users, UserPlus, Shield, Settings, Mail, Activity } from 'lucide-react'; +import { Users, UserPlus, Shield, Settings, Mail, Activity, LayoutGrid } from 'lucide-react'; import { UsersTable } from '@/components/users/UsersTable'; import { PendingUsersTable } from '@/components/users/PendingUsersTable'; import { FunctionsManager } from '@/components/users/FunctionsManager'; @@ -10,13 +10,14 @@ import { PrivilegesManager } from '@/components/users/PrivilegesManager'; import { PasswordResetRequests } from '@/components/users/PasswordResetRequests'; import { SessionLogsSimple } from '@/components/users/SessionLogsSimple'; import { UserModal } from '@/components/users/UserModal'; +import { MatrizAcessosPainel } from '@/components/users/matriz/MatrizAcessosPainel'; import { useUserManagement, UserProfile } from '@/hooks/useUserManagement'; import { usePermissionControl } from '@/hooks/usePermissionControl'; const UserManagement = () => { const [isUserModalOpen, setIsUserModalOpen] = useState(false); const [selectedUser, setSelectedUser] = useState(null); - const [activeTab, setActiveTab] = useState('users'); + const [activeTab, setActiveTab] = useState('matriz'); const { canView, @@ -157,7 +158,16 @@ const UserManagement = () => { - + + + + Matriz de Acessos + Matriz + + { )} + + + + @@ -308,7 +322,7 @@ const UserManagement = () => {