Rollback to stable frontend services commit
All checks were successful
Build and Deploy / build-and-push (push) Successful in 1m40s

This commit is contained in:
Cauê Faleiros
2026-06-09 16:00:48 -03:00
parent ed969b8c2c
commit fd0a076bc3
30 changed files with 1912 additions and 1957 deletions

View File

@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { Users, Plus, Search, X, Edit, Trash2, Loader2, AlertTriangle, KeyRound } from 'lucide-react';
import { Users, Plus, Mail, Search, X, Edit, Trash2, Loader2, AlertTriangle } from 'lucide-react';
import { getUsers, getTeams, createMember, deleteUser, updateUser, getUserById, getTenants } from '../services/dataService';
import { User, Tenant } from '../types';
@@ -24,9 +24,7 @@ export const TeamManagement: React.FC = () => {
role: 'agent' as any,
team_id: '',
status: 'active' as any,
tenant_id: '',
password: '',
confirmPassword: ''
tenant_id: ''
});
const loadData = async () => {
@@ -69,36 +67,12 @@ export const TeamManagement: React.FC = () => {
try {
const tid = localStorage.getItem('ctms_tenant_id') || '';
const finalTenantId = currentUser?.role === 'super_admin' ? formData.tenant_id : tid;
const password = formData.password.trim();
const confirmPassword = formData.confirmPassword.trim();
if (password || confirmPassword) {
if (password.length < 8) {
alert('A senha deve ter pelo menos 8 caracteres.');
return;
}
if (password !== confirmPassword) {
alert('As senhas não conferem.');
return;
}
}
const payload: any = {
name: formData.name,
email: formData.email,
role: formData.role,
team_id: formData.team_id,
status: formData.status,
tenant_id: finalTenantId
};
if (password) payload.password = password;
if (editingUser) {
const success = await updateUser(editingUser.id, payload);
const success = await updateUser(editingUser.id, { ...formData, tenant_id: finalTenantId });
if (success) { setIsModalOpen(false); loadData(); }
} else {
await createMember(payload);
await createMember({ ...formData, tenant_id: finalTenantId });
setIsModalOpen(false);
loadData();
}
@@ -120,7 +94,6 @@ export const TeamManagement: React.FC = () => {
if (loading && users.length === 0) return <div className="flex h-screen items-center justify-center text-zinc-400 dark:text-dark-muted transition-colors">Carregando...</div>;
const canManage = currentUser?.role === 'admin' || currentUser?.role === 'super_admin' || currentUser?.role === 'manager';
const canManagePasswords = currentUser?.role === 'admin' || currentUser?.role === 'super_admin';
const filtered = users.filter(u =>
(u.name.toLowerCase().includes(searchTerm.toLowerCase()) || u.email.toLowerCase().includes(searchTerm.toLowerCase())) &&
(tenantFilter === 'all' || u.tenant_id === tenantFilter)
@@ -149,7 +122,7 @@ export const TeamManagement: React.FC = () => {
<p className="text-zinc-500 dark:text-dark-muted text-sm">Visualize e gerencie as funções dos membros da sua organização.</p>
</div>
{canManage && (
<button onClick={() => { setEditingUser(null); setFormData({name:'', email:'', role:'agent', team_id:'', status:'active', tenant_id:'', password:'', confirmPassword:''}); 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">
<button onClick={() => { setEditingUser(null); setFormData({name:'', email:'', role:'agent', team_id:'', status:'active'}); 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} /> Adicionar Membro
</button>
)}
@@ -222,7 +195,7 @@ export const TeamManagement: React.FC = () => {
{canManage && (
<td className="px-6 py-4 text-right">
<div className="flex justify-end gap-2 transition-opacity">
<button onClick={() => { setEditingUser(user); setFormData({name:user.name, email:user.email, role:user.role as any, team_id:user.team_id||'', status:user.status as any, tenant_id: user.tenant_id||'', password:'', confirmPassword:''}); setIsModalOpen(true); }} className="p-2 hover:bg-zinc-100 dark:hover:bg-dark-border text-zinc-400 hover:text-zinc-900 dark:hover:text-dark-text rounded-lg transition-colors"><Edit size={16} /></button>
<button onClick={() => { setEditingUser(user); setFormData({name:user.name, email:user.email, role:user.role as any, team_id:user.team_id||'', status:user.status as any, tenant_id: user.tenant_id||''}); setIsModalOpen(true); }} className="p-2 hover:bg-zinc-100 dark:hover:bg-dark-border text-zinc-400 hover:text-zinc-900 dark:hover:text-dark-text rounded-lg transition-colors"><Edit size={16} /></button>
<button onClick={() => { setUserToDelete(user); setDeleteConfirmName(''); setIsDeleteModalOpen(true); }} className="p-2 hover:bg-red-50 dark:hover:bg-red-900/30 text-zinc-400 hover:text-red-600 dark:hover:text-red-400 rounded-lg transition-colors"><Trash2 size={16} /></button>
</div>
</td>
@@ -236,7 +209,7 @@ export const TeamManagement: React.FC = () => {
{isModalOpen && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-zinc-950/80 backdrop-blur-sm">
<div className="bg-white dark:bg-dark-card rounded-2xl p-8 w-full max-w-lg max-h-[90vh] overflow-y-auto shadow-2xl animate-in zoom-in duration-200 transition-colors border border-transparent dark:border-dark-border">
<div className="bg-white dark:bg-dark-card rounded-2xl p-8 w-full max-w-lg shadow-2xl animate-in zoom-in duration-200 transition-colors border border-transparent dark:border-dark-border">
<h3 className="text-xl font-bold text-zinc-900 dark:text-dark-text mb-6">{editingUser ? 'Editar Usuário' : 'Novo Membro'}</h3>
<form onSubmit={handleSave} className="space-y-5">
<div>
@@ -302,38 +275,6 @@ export const TeamManagement: React.FC = () => {
<option value="inactive">Inativo</option>
</select>
</div>
{canManagePasswords && (
<div className="space-y-4 rounded-xl border border-zinc-200 dark:border-dark-border bg-zinc-50 dark:bg-dark-bg/50 p-4">
<div className="flex items-center gap-2 text-xs font-bold text-zinc-500 dark:text-dark-muted uppercase">
<KeyRound size={14} className="text-brand-yellow" />
{editingUser ? 'Alterar senha' : 'Senha inicial'}
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="text-xs font-bold text-zinc-500 dark:text-dark-muted uppercase mb-1 block">Senha</label>
<input
type="password"
value={formData.password}
onChange={e => setFormData({...formData, password:e.target.value})}
className="w-full bg-white dark:bg-dark-input border border-zinc-200 dark:border-dark-border p-3 rounded-lg text-sm text-zinc-900 dark:text-dark-text outline-none focus:ring-2 focus:ring-brand-yellow/20"
placeholder={editingUser ? 'Deixe em branco para manter' : 'Opcional'}
autoComplete="new-password"
/>
</div>
<div>
<label className="text-xs font-bold text-zinc-500 dark:text-dark-muted uppercase mb-1 block">Confirmar</label>
<input
type="password"
value={formData.confirmPassword}
onChange={e => setFormData({...formData, confirmPassword:e.target.value})}
className="w-full bg-white dark:bg-dark-input border border-zinc-200 dark:border-dark-border p-3 rounded-lg text-sm text-zinc-900 dark:text-dark-text outline-none focus:ring-2 focus:ring-brand-yellow/20"
placeholder="Repita a senha"
autoComplete="new-password"
/>
</div>
</div>
</div>
)}
<div className="flex justify-end gap-3 pt-6 border-t dark:border-dark-border mt-6">
<button type="button" onClick={() => setIsModalOpen(false)} className="px-6 py-2.5 text-sm font-medium text-zinc-500 dark:text-dark-muted hover:bg-zinc-100 dark:hover:bg-dark-border rounded-lg transition-colors">Cancelar</button>
<button type="submit" disabled={isSaving} className="bg-zinc-900 dark:bg-brand-yellow text-white dark:text-zinc-950 px-8 py-2.5 rounded-lg text-sm font-bold flex items-center gap-2 hover:opacity-90 transition-all">{isSaving ? <Loader2 className="animate-spin" size={16} /> : 'Salvar Alterações'}</button>