Files
graphs/src/pages/AdminUsers.tsx
Cauê Faleiros 4bfd0d4591
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 35s
Improve users and purchase needs UI
2026-07-16 12:56:11 -03:00

755 lines
33 KiB
TypeScript

import { useEffect, useMemo, useState, type FormEvent } from 'react';
import {
AlertTriangle,
AtSign,
Check,
Copy,
Edit3,
KeyRound,
Loader2,
Mail,
Plus,
RefreshCw,
Search,
Trash2,
User as UserIcon,
UserCheck,
UserPlus,
UserX,
X
} from 'lucide-react';
import { createUser, deleteUser as deleteManagedUser, fetchUsers, updateUser } from '../dataService';
import type { ManagedUser } from '../types';
type StatusFilter = 'all' | 'active' | 'inactive';
type PasswordMode = 'auto' | 'manual';
type Notice = {
tone: 'success' | 'warning' | 'error';
text: string;
temporaryPassword?: string;
};
const statusOptions: Array<{ key: StatusFilter; label: string }> = [
{ key: 'all', label: 'Todos' },
{ key: 'active', label: 'Ativos' },
{ key: 'inactive', label: 'Inativos' }
];
const roleLabels: Record<string, string> = {
admin: 'Admin',
supervisor: 'Supervisor',
producao: 'Produção',
operacao: 'Operação',
vendedor: 'Vendedor',
financeiro: 'Financeiro',
user: 'Usuário'
};
const inputWrapClassName = 'flex h-11 items-center gap-3 rounded-lg border border-dark-border bg-dark-input px-3 transition-colors focus-within:border-brand-primary focus-within:ring-1 focus-within:ring-brand-primary';
const inputClassName = 'min-w-0 flex-1 bg-transparent text-sm font-semibold text-dark-text outline-none placeholder:text-dark-muted';
const getInitials = (name: string) => {
const parts = name.trim().split(/\s+/).filter(Boolean);
if (!parts.length) return '?';
return parts.slice(0, 2).map((part) => part[0]).join('').toUpperCase();
};
const getUserRoleLabel = (user: ManagedUser) => {
const role = String((user as ManagedUser & { role?: string }).role || 'user').toLowerCase();
return roleLabels[role] || role;
};
const AdminUsers = () => {
const [users, setUsers] = useState<ManagedUser[]>([]);
const [searchTerm, setSearchTerm] = useState('');
const [statusFilter, setStatusFilter] = useState<StatusFilter>('all');
const [isLoading, setIsLoading] = useState(true);
const [isModalOpen, setIsModalOpen] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [editingUser, setEditingUser] = useState<ManagedUser | null>(null);
const [userToDelete, setUserToDelete] = useState<ManagedUser | null>(null);
const [passwordMode, setPasswordMode] = useState<PasswordMode>('auto');
const [isActive, setIsActive] = useState(true);
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [notice, setNotice] = useState<Notice | null>(null);
const [copyLabel, setCopyLabel] = useState('Copiar');
const activeCount = useMemo(() => users.filter((user) => user.isActive).length, [users]);
const inactiveCount = users.length - activeCount;
const filteredUsers = useMemo(() => {
const query = searchTerm.trim().toLowerCase();
return users.filter((user) => {
const matchesStatus =
statusFilter === 'all' ||
(statusFilter === 'active' && user.isActive) ||
(statusFilter === 'inactive' && !user.isActive);
const matchesQuery =
!query ||
user.name.toLowerCase().includes(query) ||
user.email.toLowerCase().includes(query);
return matchesStatus && matchesQuery;
});
}, [searchTerm, statusFilter, users]);
const hasFilters = searchTerm.trim() !== '' || statusFilter !== 'all';
const loadUsers = async () => {
setIsLoading(true);
try {
setUsers(await fetchUsers());
} finally {
setIsLoading(false);
}
};
useEffect(() => {
// User management needs an initial API load when the admin page opens.
// eslint-disable-next-line react-hooks/set-state-in-effect
void loadUsers();
}, []);
const resetForm = () => {
setName('');
setEmail('');
setPassword('');
setPasswordMode('auto');
setIsActive(true);
setEditingUser(null);
setNotice(null);
setCopyLabel('Copiar');
};
const closeModal = () => {
if (isSubmitting) return;
setIsModalOpen(false);
resetForm();
};
const clearFilters = () => {
setSearchTerm('');
setStatusFilter('all');
};
const openCreateModal = () => {
resetForm();
setIsModalOpen(true);
};
const openEditModal = (user: ManagedUser) => {
setEditingUser(user);
setName(user.name);
setEmail(user.email);
setIsActive(user.isActive);
setPassword('');
setPasswordMode('auto');
setNotice(null);
setCopyLabel('Copiar');
setIsModalOpen(true);
};
const handleCreateUser = async (event: FormEvent) => {
event.preventDefault();
setIsSubmitting(true);
setNotice(null);
setCopyLabel('Copiar');
try {
const result = await createUser({
name,
email,
password: passwordMode === 'manual' ? password : undefined
});
setUsers((currentUsers) => [result.user, ...currentUsers]);
if (result.temporaryPassword) {
setNotice({
tone: 'success',
text: 'Acesso criado. Copie a senha gerada antes de fechar.',
temporaryPassword: result.temporaryPassword
});
setName('');
setEmail('');
setPassword('');
setPasswordMode('auto');
} else {
setNotice({
tone: 'success',
text: 'Acesso criado com a senha definida.'
});
window.setTimeout(() => {
setIsModalOpen(false);
resetForm();
}, 900);
}
} catch (error) {
setNotice({
tone: 'error',
text: error instanceof Error ? error.message : 'Não foi possível criar o acesso.'
});
} finally {
setIsSubmitting(false);
}
};
const handleUpdateUser = async (event: FormEvent) => {
event.preventDefault();
if (!editingUser) return;
setIsSubmitting(true);
setNotice(null);
try {
const updatedUser = await updateUser(editingUser.id, {
name,
email,
isActive,
password: passwordMode === 'manual' ? password : undefined
});
setUsers((currentUsers) => currentUsers.map((user) => user.id === updatedUser.id ? updatedUser : user));
setNotice({
tone: 'success',
text: 'Usuário atualizado.'
});
window.setTimeout(() => {
setIsModalOpen(false);
resetForm();
}, 700);
} catch (error) {
setNotice({
tone: 'error',
text: error instanceof Error ? error.message : 'Não foi possível editar o usuário.'
});
} finally {
setIsSubmitting(false);
}
};
const handleDeleteUser = async () => {
if (!userToDelete) return;
setIsSubmitting(true);
try {
await deleteManagedUser(userToDelete.id);
setUsers((currentUsers) => currentUsers.filter((user) => user.id !== userToDelete.id));
setUserToDelete(null);
} catch (error) {
setNotice({
tone: 'error',
text: error instanceof Error ? error.message : 'Não foi possível excluir o usuário.'
});
} finally {
setIsSubmitting(false);
}
};
const copyTemporaryPassword = async () => {
if (!notice?.temporaryPassword) return;
try {
await navigator.clipboard.writeText(notice.temporaryPassword);
setCopyLabel('Copiado');
window.setTimeout(() => setCopyLabel('Copiar'), 1600);
} catch {
setCopyLabel('Falhou');
}
};
const noticeClass = notice?.tone === 'error'
? 'border-red-500/30 bg-red-500/10 text-red-300'
: notice?.tone === 'warning'
? 'border-amber-500/30 bg-amber-500/10 text-amber-200'
: 'border-emerald-500/30 bg-emerald-500/10 text-emerald-300';
const statusCounts: Record<StatusFilter, number> = {
all: users.length,
active: activeCount,
inactive: inactiveCount
};
const stats = [
{ label: 'Total', value: users.length, icon: UserIcon, tone: 'text-dark-text', detail: 'acessos cadastrados' },
{ label: 'Ativos', value: activeCount, icon: UserCheck, tone: 'text-emerald-300', detail: 'podem entrar agora' },
{ label: 'Inativos', value: inactiveCount, icon: UserX, tone: 'text-red-300', detail: 'bloqueados no login' },
{ label: 'Exibindo', value: filteredUsers.length, icon: Search, tone: 'text-brand-primary', detail: hasFilters ? 'após filtros' : 'sem filtros' }
];
return (
<div className="space-y-5">
<header className="flex flex-col gap-4 xl:flex-row xl:items-end xl:justify-between">
<div className="max-w-3xl">
<h1 className="text-3xl font-bold text-dark-text">Usuários</h1>
<p className="mt-2 text-sm leading-6 text-dark-muted">
Gerencie acessos individuais, senhas iniciais e bloqueios de entrada no painel.
</p>
</div>
<div className="flex flex-col gap-2 sm:flex-row sm:items-center">
<button
type="button"
onClick={() => void loadUsers()}
disabled={isLoading}
className="inline-flex h-10 cursor-pointer items-center justify-center gap-2 rounded-lg border border-dark-border bg-dark-card px-4 text-sm font-bold text-dark-text transition-colors hover:border-brand-primary disabled:cursor-not-allowed disabled:opacity-50"
>
<RefreshCw className={`h-4 w-4 ${isLoading ? 'animate-spin' : ''}`} />
Atualizar
</button>
<button
type="button"
onClick={openCreateModal}
className="inline-flex h-10 cursor-pointer items-center justify-center gap-2 rounded-lg bg-brand-primary px-4 text-sm font-bold text-brand-contrast transition-colors hover:bg-brand-primary/90"
>
<UserPlus className="h-4 w-4" />
Novo acesso
</button>
</div>
</header>
<section className="grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-4">
{stats.map((stat) => {
const Icon = stat.icon;
return (
<div key={stat.label} className="rounded-2xl border border-dark-border bg-dark-card p-4 shadow-sm">
<div className="flex items-start justify-between gap-3">
<div>
<p className="text-xs font-bold uppercase tracking-widest text-dark-muted">{stat.label}</p>
<p className={`mt-2 text-2xl font-bold ${stat.tone}`}>{stat.value}</p>
<p className="mt-1 text-xs font-semibold text-dark-muted">{stat.detail}</p>
</div>
<div className="flex h-10 w-10 items-center justify-center rounded-lg border border-dark-border bg-dark-input text-dark-muted">
<Icon className="h-5 w-5" />
</div>
</div>
</div>
);
})}
</section>
<section className="overflow-hidden rounded-2xl border border-dark-border bg-dark-card shadow-sm">
<div className="border-b border-dark-border p-4">
<div className="flex flex-col gap-4 xl:flex-row xl:items-center xl:justify-between">
<div>
<h2 className="text-base font-bold text-dark-text">Acessos cadastrados</h2>
<p className="mt-1 text-sm text-dark-muted">Busque, filtre e edite os usuários do painel.</p>
</div>
<div className="flex flex-col gap-3 lg:flex-row lg:items-center">
<div className="flex h-10 min-w-0 items-center gap-2 rounded-lg border border-dark-border bg-dark-input px-3 lg:w-80">
<Search className="h-4 w-4 shrink-0 text-dark-muted" />
<input
type="search"
value={searchTerm}
onChange={(event) => setSearchTerm(event.target.value)}
className="min-w-0 flex-1 bg-transparent text-sm font-semibold text-dark-text outline-none placeholder:text-dark-muted"
placeholder="Buscar nome ou e-mail"
/>
{searchTerm && (
<button
type="button"
onClick={() => setSearchTerm('')}
className="flex h-6 w-6 cursor-pointer items-center justify-center rounded-md text-dark-muted transition-colors hover:bg-dark-card hover:text-dark-text"
aria-label="Limpar busca"
title="Limpar busca"
>
<X className="h-3.5 w-3.5" />
</button>
)}
</div>
<div className="grid grid-cols-3 rounded-lg border border-dark-border bg-dark-input p-1">
{statusOptions.map((option) => (
<button
key={option.key}
type="button"
onClick={() => setStatusFilter(option.key)}
className={`h-8 cursor-pointer rounded-md px-3 text-sm font-semibold transition-colors ${
statusFilter === option.key
? 'bg-dark-card text-dark-text shadow-sm'
: 'text-dark-muted hover:text-dark-text'
}`}
>
{option.label}
<span className="ml-1 text-xs opacity-70">{statusCounts[option.key]}</span>
</button>
))}
</div>
</div>
</div>
</div>
{isLoading ? (
<div className="divide-y divide-dark-border" aria-label="Carregando usuários">
{Array.from({ length: 5 }).map((_, index) => (
<div key={`user-skeleton-${index}`} className="grid grid-cols-[1.4fr_1fr_120px_140px_88px] gap-5 px-5 py-4">
{Array.from({ length: 5 }).map((__, column) => (
<div key={`user-skeleton-${index}-${column}`} className="skeleton h-4" />
))}
</div>
))}
</div>
) : users.length === 0 ? (
<div className="flex min-h-[360px] flex-col items-center justify-center px-6 py-12 text-center">
<div className="mb-5 flex h-14 w-14 items-center justify-center rounded-2xl border border-dark-border bg-dark-input text-brand-primary">
<UserPlus className="h-7 w-7" />
</div>
<p className="text-lg font-bold text-dark-text">Nenhum usuário cadastrado</p>
<p className="mt-2 max-w-md text-sm leading-6 text-dark-muted">Crie o primeiro acesso individual para tirar o uso compartilhado do login administrativo.</p>
<button
type="button"
onClick={openCreateModal}
className="mt-5 inline-flex h-10 cursor-pointer items-center justify-center gap-2 rounded-lg bg-brand-primary px-4 text-sm font-bold text-brand-contrast transition-colors hover:bg-brand-primary/90"
>
<UserPlus className="h-4 w-4" />
Criar primeiro acesso
</button>
</div>
) : filteredUsers.length === 0 ? (
<div className="flex min-h-[320px] flex-col items-center justify-center px-6 py-12 text-center">
<div className="mb-5 flex h-14 w-14 items-center justify-center rounded-2xl border border-dark-border bg-dark-input text-dark-muted">
<Search className="h-7 w-7" />
</div>
<p className="text-lg font-bold text-dark-text">Nenhum resultado</p>
<p className="mt-2 max-w-md text-sm leading-6 text-dark-muted">Não encontramos usuários com os filtros atuais.</p>
<button
type="button"
onClick={clearFilters}
className="mt-5 inline-flex h-10 cursor-pointer items-center justify-center rounded-lg border border-dark-border px-4 text-sm font-bold text-dark-text transition-colors hover:border-brand-primary"
>
Limpar filtros
</button>
</div>
) : (
<div className="overflow-x-auto">
<table className="w-full min-w-[900px] table-fixed text-left">
<thead className="border-b border-dark-border bg-dark-input/35 text-xs uppercase tracking-widest text-dark-muted">
<tr>
<th className="w-[34%] px-5 py-3 font-bold">Usuário</th>
<th className="w-[24%] px-5 py-3 font-bold">E-mail</th>
<th className="w-[16%] px-5 py-3 font-bold">Perfil</th>
<th className="w-[14%] px-5 py-3 font-bold">Status</th>
<th className="w-[12%] px-5 py-3 text-right font-bold">Ações</th>
</tr>
</thead>
<tbody className="divide-y divide-dark-border">
{filteredUsers.map((user) => (
<tr key={user.id} className="text-sm transition-colors hover:bg-dark-input/45">
<td className="px-5 py-4">
<div className="flex min-w-0 items-center gap-3">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-brand-primary/10 text-sm font-bold text-brand-primary">
{getInitials(user.name)}
</div>
<div className="min-w-0">
<p className="truncate font-bold text-dark-text">{user.name}</p>
<p className="mt-0.5 text-xs font-semibold text-dark-muted">ID {user.id}</p>
</div>
</div>
</td>
<td className="px-5 py-4">
<div className="flex min-w-0 items-center gap-2 text-dark-muted">
<Mail className="h-4 w-4 shrink-0" />
<span className="truncate font-semibold">{user.email}</span>
</div>
</td>
<td className="px-5 py-4">
<span className="inline-flex rounded-full border border-brand-primary/20 bg-brand-primary/10 px-2.5 py-1 text-xs font-bold text-brand-primary">
{getUserRoleLabel(user)}
</span>
</td>
<td className="px-5 py-4">
<span className={`inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-bold ${
user.isActive ? 'bg-emerald-500/10 text-emerald-300' : 'bg-red-500/10 text-red-300'
}`}>
<span className={`h-1.5 w-1.5 rounded-full ${user.isActive ? 'bg-emerald-300' : 'bg-red-300'}`} />
{user.isActive ? 'Ativo' : 'Inativo'}
</span>
</td>
<td className="px-5 py-4">
<div className="flex justify-end gap-2">
<button
type="button"
onClick={() => openEditModal(user)}
className="flex h-9 w-9 cursor-pointer items-center justify-center rounded-lg border border-dark-border text-dark-muted transition-colors hover:border-brand-primary/50 hover:text-brand-primary"
aria-label={`Editar ${user.name}`}
title="Editar"
>
<Edit3 className="h-4 w-4" />
</button>
<button
type="button"
onClick={() => {
setNotice(null);
setUserToDelete(user);
}}
className="flex h-9 w-9 cursor-pointer items-center justify-center rounded-lg border border-dark-border text-dark-muted transition-colors hover:border-red-400/50 hover:text-red-300"
aria-label={`Excluir ${user.name}`}
title="Excluir"
>
<Trash2 className="h-4 w-4" />
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</section>
{isModalOpen && (
<div className="fixed inset-0 z-50 flex items-center justify-center overflow-y-auto bg-black/70 px-4 py-6">
<div className="w-full max-w-2xl overflow-hidden rounded-2xl border border-dark-border bg-dark-card shadow-2xl">
<div className="flex items-start justify-between gap-4 border-b border-dark-border px-5 py-4">
<div className="flex items-start gap-3">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl border border-dark-border bg-dark-input text-brand-primary">
{editingUser ? <Edit3 className="h-5 w-5" /> : <UserPlus className="h-5 w-5" />}
</div>
<div>
<h2 className="text-lg font-bold text-dark-text">{editingUser ? 'Editar usuário' : 'Novo acesso'}</h2>
<p className="mt-1 text-sm leading-5 text-dark-muted">
{editingUser ? 'Atualize identificação, status e senha.' : 'Crie uma credencial individual para o painel.'}
</p>
</div>
</div>
<button
type="button"
onClick={closeModal}
className="flex h-9 w-9 cursor-pointer items-center justify-center rounded-lg text-dark-muted transition-colors hover:bg-dark-input hover:text-dark-text"
aria-label="Fechar"
title="Fechar"
>
<X className="h-5 w-5" />
</button>
</div>
<form onSubmit={editingUser ? handleUpdateUser : handleCreateUser}>
<div className="space-y-5 p-5">
<section>
<div className="mb-3">
<h3 className="text-sm font-bold text-dark-text">Identificação</h3>
<p className="mt-1 text-xs font-semibold text-dark-muted">Use um nome reconhecível e um e-mail individual.</p>
</div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<label className="block">
<span className="mb-2 block text-xs font-bold uppercase tracking-widest text-dark-muted">Nome</span>
<div className={inputWrapClassName}>
<UserIcon className="h-4 w-4 shrink-0 text-dark-muted" />
<input
type="text"
value={name}
onChange={(event) => setName(event.target.value)}
className={inputClassName}
placeholder="Nome completo"
required
autoFocus
/>
</div>
</label>
<label className="block">
<span className="mb-2 block text-xs font-bold uppercase tracking-widest text-dark-muted">E-mail</span>
<div className={inputWrapClassName}>
<AtSign className="h-4 w-4 shrink-0 text-dark-muted" />
<input
type="email"
value={email}
onChange={(event) => setEmail(event.target.value)}
className={inputClassName}
placeholder="usuario@empresa.com"
required
/>
</div>
</label>
</div>
</section>
<section>
<div className="mb-3">
<h3 className="text-sm font-bold text-dark-text">{editingUser ? 'Senha' : 'Senha inicial'}</h3>
<p className="mt-1 text-xs font-semibold text-dark-muted">
{editingUser ? 'Mantenha a senha atual ou defina uma nova.' : 'Gere uma senha temporária ou defina uma senha manual.'}
</p>
</div>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
<button
type="button"
onClick={() => setPasswordMode('auto')}
className={`cursor-pointer rounded-xl border p-4 text-left transition-colors ${
passwordMode === 'auto'
? 'border-brand-primary/50 bg-brand-primary/10 text-dark-text'
: 'border-dark-border bg-dark-input text-dark-muted hover:border-brand-primary/35 hover:text-dark-text'
}`}
>
<span className="flex items-center gap-2 text-sm font-bold">
<KeyRound className="h-4 w-4" />
{editingUser ? 'Manter senha' : 'Gerar senha'}
</span>
<span className="mt-1 block text-xs font-semibold opacity-80">
{editingUser ? 'Não altera a credencial atual.' : 'Mostra uma senha para copiar após criar.'}
</span>
</button>
<button
type="button"
onClick={() => setPasswordMode('manual')}
className={`cursor-pointer rounded-xl border p-4 text-left transition-colors ${
passwordMode === 'manual'
? 'border-brand-primary/50 bg-brand-primary/10 text-dark-text'
: 'border-dark-border bg-dark-input text-dark-muted hover:border-brand-primary/35 hover:text-dark-text'
}`}
>
<span className="flex items-center gap-2 text-sm font-bold">
<Edit3 className="h-4 w-4" />
{editingUser ? 'Alterar senha' : 'Definir senha'}
</span>
<span className="mt-1 block text-xs font-semibold opacity-80">
Use quando a senha será combinada fora do sistema.
</span>
</button>
</div>
{passwordMode === 'manual' && (
<div className="mt-3">
<div className={inputWrapClassName}>
<KeyRound className="h-4 w-4 shrink-0 text-dark-muted" />
<input
type="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
className={inputClassName}
placeholder="Mínimo de 6 caracteres"
minLength={6}
required
/>
</div>
</div>
)}
</section>
{editingUser && (
<label className="flex cursor-pointer items-center justify-between gap-4 rounded-xl border border-dark-border bg-dark-input px-4 py-3">
<div>
<span className="block text-sm font-bold text-dark-text">Usuário ativo</span>
<span className="mt-0.5 block text-xs font-semibold text-dark-muted">Usuários inativos não conseguem entrar.</span>
</div>
<input
type="checkbox"
checked={isActive}
onChange={(event) => setIsActive(event.target.checked)}
className="h-5 w-5 cursor-pointer accent-brand-primary"
/>
</label>
)}
{notice && (
<div className={`rounded-xl border px-3 py-3 text-sm font-semibold ${noticeClass}`}>
<div className="flex items-start gap-2">
{notice.tone === 'error' ? <X className="mt-0.5 h-4 w-4 shrink-0" /> : <Check className="mt-0.5 h-4 w-4 shrink-0" />}
<div className="min-w-0 flex-1">
<p>{notice.text}</p>
{notice.temporaryPassword && (
<div className="mt-3 flex flex-col gap-2 sm:flex-row">
<code className="min-w-0 flex-1 truncate rounded-lg bg-black/25 px-3 py-2 text-amber-100">
{notice.temporaryPassword}
</code>
<button
type="button"
onClick={copyTemporaryPassword}
className="inline-flex h-9 cursor-pointer items-center justify-center gap-2 rounded-lg border border-amber-400/30 px-3 text-xs font-bold text-amber-100 transition-colors hover:bg-amber-400/10"
>
<Copy className="h-3.5 w-3.5" />
{copyLabel}
</button>
</div>
)}
</div>
</div>
</div>
)}
</div>
<div className="flex flex-col-reverse gap-2 border-t border-dark-border bg-dark-input/35 px-5 py-4 sm:flex-row sm:justify-end">
<button
type="button"
onClick={closeModal}
disabled={isSubmitting}
className="h-10 cursor-pointer rounded-lg border border-dark-border px-4 text-sm font-bold text-dark-muted transition-colors hover:text-dark-text disabled:cursor-not-allowed disabled:opacity-50"
>
Cancelar
</button>
<button
type="submit"
disabled={isSubmitting}
className="inline-flex h-10 cursor-pointer items-center justify-center gap-2 rounded-lg bg-brand-primary px-4 text-sm font-bold text-brand-contrast transition-colors hover:bg-brand-primary/90 disabled:cursor-not-allowed disabled:opacity-50"
>
{isSubmitting ? <Loader2 className="h-4 w-4 animate-spin" /> : <Plus className="h-4 w-4" />}
{editingUser ? 'Salvar alterações' : 'Criar acesso'}
</button>
</div>
</form>
</div>
</div>
)}
{userToDelete && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 px-4 py-6">
<div className="w-full max-w-md overflow-hidden rounded-2xl border border-dark-border bg-dark-card shadow-2xl">
<div className="flex items-start gap-3 border-b border-dark-border px-5 py-4">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl border border-red-500/25 bg-red-500/10 text-red-300">
<AlertTriangle className="h-5 w-5" />
</div>
<div>
<h2 className="text-lg font-bold text-dark-text">Excluir usuário</h2>
<p className="mt-1 text-sm leading-5 text-dark-muted">Esta ação remove o acesso imediatamente.</p>
</div>
</div>
<div className="space-y-4 p-5">
<div className="rounded-xl border border-dark-border bg-dark-input p-4">
<p className="font-bold text-dark-text">{userToDelete.name}</p>
<p className="mt-1 text-sm font-semibold text-dark-muted">{userToDelete.email}</p>
</div>
{notice?.tone === 'error' && (
<div className={`rounded-xl border px-3 py-3 text-sm font-semibold ${noticeClass}`}>
{notice.text}
</div>
)}
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
<button
type="button"
onClick={() => {
if (isSubmitting) return;
setUserToDelete(null);
setNotice(null);
}}
disabled={isSubmitting}
className="h-10 cursor-pointer rounded-lg border border-dark-border px-4 text-sm font-bold text-dark-muted transition-colors hover:text-dark-text disabled:cursor-not-allowed disabled:opacity-50"
>
Cancelar
</button>
<button
type="button"
onClick={handleDeleteUser}
disabled={isSubmitting}
className="inline-flex h-10 cursor-pointer items-center justify-center gap-2 rounded-lg bg-red-500 px-4 text-sm font-bold text-white transition-colors hover:bg-red-400 disabled:cursor-not-allowed disabled:opacity-50"
>
{isSubmitting ? <Loader2 className="h-4 w-4 animate-spin" /> : <Trash2 className="h-4 w-4" />}
Excluir
</button>
</div>
</div>
</div>
</div>
)}
</div>
);
};
export default AdminUsers;