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.
This commit is contained in:
@@ -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<any[]>([]);
|
||||
const [users, setUsers] = useState<User[]>([]);
|
||||
const [attendances, setAttendances] = useState<Attendance[]>([]);
|
||||
const [currentUser, setCurrentUser] = useState<User | null>(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 <div className="p-12 text-center text-zinc-400 dark:text-dark-muted transition-colors">Carregando...</div>;
|
||||
|
||||
const canManage = currentUser?.role === 'admin' || currentUser?.role === 'super_admin';
|
||||
|
||||
return (
|
||||
<div className="space-y-8 max-w-7xl mx-auto pb-12 transition-colors duration-300">
|
||||
<div className="flex justify-between items-center">
|
||||
@@ -58,9 +64,11 @@ export const Teams: React.FC = () => {
|
||||
<h1 className="text-2xl font-bold text-zinc-900 dark:text-zinc-50 tracking-tight">Times</h1>
|
||||
<p className="text-zinc-500 dark:text-dark-muted text-sm">Visualize o desempenho e gerencie seus grupos de vendas.</p>
|
||||
</div>
|
||||
<button onClick={() => { setEditingTeam(null); setFormData({name:'', description:''}); setIsModalOpen(true); }} className="bg-zinc-900 dark:bg-brand-yellow text-white dark:text-zinc-950 px-4 py-2 rounded-lg flex items-center gap-2 text-sm font-bold shadow-sm hover:opacity-90 transition-all">
|
||||
<Plus size={16} /> Novo Time
|
||||
</button>
|
||||
{canManage && (
|
||||
<button onClick={() => { setEditingTeam(null); setFormData({name:'', description:''}); setIsModalOpen(true); }} className="bg-zinc-900 dark:bg-brand-yellow text-white dark:text-zinc-950 px-4 py-2 rounded-lg flex items-center gap-2 text-sm font-bold shadow-sm hover:opacity-90 transition-all">
|
||||
<Plus size={16} /> Novo Time
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
@@ -68,7 +76,9 @@ export const Teams: React.FC = () => {
|
||||
<div key={t.id} className="bg-white dark:bg-dark-card rounded-2xl border border-zinc-200 dark:border-dark-border p-6 shadow-sm group hover:shadow-md transition-all">
|
||||
<div className="flex justify-between mb-6">
|
||||
<div className="p-3 bg-zinc-50 dark:bg-dark-bg text-zinc-600 dark:text-dark-muted rounded-xl border border-zinc-100 dark:border-dark-border"><Building2 size={24} /></div>
|
||||
<button onClick={() => { setEditingTeam(t); setFormData({name:t.name, description:t.description||''}); setIsModalOpen(true); }} className="text-zinc-400 dark:text-dark-muted hover:text-zinc-900 dark:hover:text-dark-text opacity-0 group-hover:opacity-100 p-2 rounded-lg hover:bg-zinc-50 dark:hover:bg-dark-bg transition-all"><Edit2 size={18} /></button>
|
||||
{canManage && (
|
||||
<button onClick={() => { setEditingTeam(t); setFormData({name:t.name, description:t.description||''}); setIsModalOpen(true); }} className="text-zinc-400 dark:text-dark-muted hover:text-zinc-900 dark:hover:text-dark-text opacity-0 group-hover:opacity-100 p-2 rounded-lg hover:bg-zinc-50 dark:hover:bg-dark-bg transition-all"><Edit2 size={18} /></button>
|
||||
)}
|
||||
</div>
|
||||
<h3 className="text-lg font-bold text-zinc-900 dark:text-zinc-50 mb-1">{t.name}</h3>
|
||||
<p className="text-sm text-zinc-500 dark:text-dark-muted mb-6 h-10 line-clamp-2">{t.description || 'Sem descrição definida.'}</p>
|
||||
|
||||
Reference in New Issue
Block a user