From 13b4c0316bc31a2a495f261fc11a442cc1a7a81c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Faleiros?= Date: Fri, 6 Mar 2026 14:54:42 -0300 Subject: [PATCH] feat: restrict managers to their own team - Backend now only returns users, teams, and attendances from a manager's own team. - Hidden 'Todas as Equipes' filter from manager dashboard. - Removed manager ability to create or edit teams. --- backend/index.js | 51 ++++++++++++++++++++++++++++++++++----------- pages/Dashboard.tsx | 18 +++++++++------- pages/Teams.tsx | 24 ++++++++++++++------- 3 files changed, 66 insertions(+), 27 deletions(-) diff --git a/backend/index.js b/backend/index.js index e24e156..ef69434 100644 --- a/backend/index.js +++ b/backend/index.js @@ -286,7 +286,17 @@ apiRouter.get('/users', async (req, res) => { let q = 'SELECT * FROM users'; const params = []; - if (effectiveTenantId && effectiveTenantId !== 'all') { q += ' WHERE tenant_id = ?'; params.push(effectiveTenantId); } + if (effectiveTenantId && effectiveTenantId !== 'all') { + q += ' WHERE tenant_id = ?'; + params.push(effectiveTenantId); + } + + // Strict RBAC: Managers can only see users in their own team + if (req.user.role === 'manager') { + q += (params.length > 0 ? ' AND' : ' WHERE') + ' team_id = ?'; + params.push(req.user.team_id); + } + const [rows] = await pool.query(q, params); res.json(rows); } catch (error) { res.status(500).json({ error: error.message }); } @@ -447,17 +457,27 @@ apiRouter.get('/attendances', async (req, res) => { if (req.user.role === 'agent') { q += ' AND a.user_id = ?'; params.push(req.user.id); - } else if (userId && userId !== 'all') { - // check if it's a slug or id - if (userId.startsWith('u_')) { - q += ' AND a.user_id = ?'; - params.push(userId); - } else { - q += ' AND u.slug = ?'; - params.push(userId); + } else { + if (req.user.role === 'manager') { + q += ' AND u.team_id = ?'; + params.push(req.user.team_id); + } else if (teamId && teamId !== 'all') { + q += ' AND u.team_id = ?'; + params.push(teamId); + } + + if (userId && userId !== 'all') { + // check if it's a slug or id + if (userId.startsWith('u_')) { + q += ' AND a.user_id = ?'; + params.push(userId); + } else { + q += ' AND u.slug = ?'; + params.push(userId); + } } } - if (teamId && teamId !== 'all') { q += ' AND u.team_id = ?'; params.push(teamId); } + if (funnelStage && funnelStage !== 'all') { q += ' AND a.funnel_stage = ?'; params.push(funnelStage); } if (origin && origin !== 'all') { q += ' AND a.origin = ?'; params.push(origin); } @@ -513,6 +533,13 @@ apiRouter.get('/teams', async (req, res) => { q += ' WHERE tenant_id = ?'; params.push(effectiveTenantId); } + + // Strict RBAC: Managers can only see their own team + if (req.user.role === 'manager') { + q += (params.length > 0 ? ' AND' : ' WHERE') + ' id = ?'; + params.push(req.user.team_id); + } + const [rows] = await pool.query(q, params); res.json(rows); } catch (error) { @@ -520,7 +547,7 @@ apiRouter.get('/teams', async (req, res) => { } }); -apiRouter.post('/teams', requireRole(['admin', 'manager', 'owner', 'super_admin']), async (req, res) => { +apiRouter.post('/teams', requireRole(['admin', 'owner', 'super_admin']), async (req, res) => { const { name, description, tenantId } = req.body; const effectiveTenantId = req.user.role === 'super_admin' ? tenantId : req.user.tenant_id; try { @@ -536,7 +563,7 @@ apiRouter.post('/teams', requireRole(['admin', 'manager', 'owner', 'super_admin' } }); -apiRouter.put('/teams/:id', requireRole(['admin', 'manager', 'owner', 'super_admin']), async (req, res) => { +apiRouter.put('/teams/:id', requireRole(['admin', 'owner', 'super_admin']), async (req, res) => { const { name, description } = req.body; try { const [existing] = await pool.query('SELECT tenant_id FROM teams WHERE id = ?', [req.params.id]); diff --git a/pages/Dashboard.tsx b/pages/Dashboard.tsx index 3218f86..b7e597d 100644 --- a/pages/Dashboard.tsx +++ b/pages/Dashboard.tsx @@ -253,14 +253,16 @@ export const Dashboard: React.FC = () => { {users.map(u => )} - + {currentUser?.role !== 'manager' && ( + + )} )} diff --git a/pages/Teams.tsx b/pages/Teams.tsx index ab4a3d6..b72b85c 100644 --- a/pages/Teams.tsx +++ b/pages/Teams.tsx @@ -1,12 +1,13 @@ import React, { useState, useEffect, useMemo } from 'react'; import { Building2, Users, Plus, Search, Target, ArrowUpRight, Loader2, Edit2, X } from 'lucide-react'; -import { getTeams, getUsers, getAttendances, createTeam, updateTeam } from '../services/dataService'; +import { getTeams, getUsers, getAttendances, createTeam, updateTeam, getUserById } from '../services/dataService'; import { User, Attendance } from '../types'; export const Teams: React.FC = () => { const [teams, setTeams] = useState([]); const [users, setUsers] = useState([]); const [attendances, setAttendances] = useState([]); + const [currentUser, setCurrentUser] = useState(null); const [loading, setLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false); @@ -15,15 +16,18 @@ export const Teams: React.FC = () => { const loadData = async () => { const tid = localStorage.getItem('ctms_tenant_id'); + const uid = localStorage.getItem('ctms_user_id'); if (!tid) return; setLoading(true); try { - const [ft, fu, fa] = await Promise.all([ + const [ft, fu, fa, me] = await Promise.all([ getTeams(tid), getUsers(tid), - getAttendances(tid, { dateRange: { start: new Date(0), end: new Date() }, userId: 'all', teamId: 'all' }) + getAttendances(tid, { dateRange: { start: new Date(0), end: new Date() }, userId: 'all', teamId: 'all' }), + uid ? getUserById(uid) : null ]); setTeams(ft); setUsers(fu); setAttendances(fa); + if (me) setCurrentUser(me); } catch (e) { console.error(e); } finally { setLoading(false); } }; @@ -51,6 +55,8 @@ export const Teams: React.FC = () => { if (loading && teams.length === 0) return
Carregando...
; + const canManage = currentUser?.role === 'admin' || currentUser?.role === 'super_admin'; + return (
@@ -58,9 +64,11 @@ export const Teams: React.FC = () => {

Times

Visualize o desempenho e gerencie seus grupos de vendas.

- + {canManage && ( + + )}
@@ -68,7 +76,9 @@ export const Teams: React.FC = () => {
- + {canManage && ( + + )}

{t.name}

{t.description || 'Sem descrição definida.'}