transcription-cost-usage-re.../frontend/components/user-management.tsx

510 lines
17 KiB
TypeScript

"use client"
import { useState, useEffect } from "react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Alert, AlertDescription } from "@/components/ui/alert"
import { Badge } from "@/components/ui/badge"
import { Checkbox } from "@/components/ui/checkbox"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Edit2, Plus, Save, X, Loader2, RefreshCw, Users, Shield, Trash2 } from "lucide-react"
import { isAdmin, getCurrentUserEmail } from "@/lib/auth"
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:5000/api/v1"
interface User {
_id: {
$oid: string
}
email: string
roles: string[]
}
interface UsersResponse {
success: boolean
data: User[]
}
interface CreateUserRequest {
email: string
password: string
roles: string[]
}
interface UpdateUserRequest {
email: string
password?: string
roles: string[]
}
// Roles disponíveis no sistema
const AVAILABLE_ROLES = [
{ id: "admin", label: "Administrador", description: "Acesso total ao sistema" },
{ id: "user", label: "Usuário", description: "Acesso parcial, não pode gerenciar usuários" },
]
export default function UserManagement() {
const [users, setUsers] = useState<User[]>([])
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState("")
const [success, setSuccess] = useState("")
// Estados para criação de usuário
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false)
const [isCreating, setIsCreating] = useState(false)
const [createForm, setCreateForm] = useState<CreateUserRequest>({
email: "",
password: "",
roles: [],
})
// Estados para edição de usuário
const [editingUser, setEditingUser] = useState<string | null>(null)
const [editForm, setEditForm] = useState<UpdateUserRequest>({
email: "",
password: "",
roles: [],
})
const [isUpdating, setIsUpdating] = useState(false)
// Estados para exclusão
const [isDeleting, setIsDeleting] = useState<string | null>(null)
const getAuthHeaders = () => {
const token = localStorage.getItem("access_token")
return {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
}
}
const fetchUsers = async () => {
setIsLoading(true)
setError("")
try {
const response = await fetch(`${API_BASE_URL}/users`, {
method: "GET",
headers: getAuthHeaders(),
})
if (response.ok) {
const result: UsersResponse = await response.json()
setUsers(result.data || [])
} else {
const errorData = await response.json()
setError(errorData.message || "Erro ao buscar usuários")
}
} catch (err: unknown) {
console.log(err)
setError("Erro de conexão com o servidor")
} finally {
setIsLoading(false)
}
}
const createUser = async () => {
if (!createForm.email || !createForm.password || createForm.roles.length === 0) {
setError("Preencha todos os campos obrigatórios")
return
}
setIsCreating(true)
setError("")
setSuccess("")
try {
const response = await fetch(`${API_BASE_URL}/auth/signup`, {
method: "POST",
headers: getAuthHeaders(),
body: JSON.stringify(createForm),
})
if (response.ok) {
setSuccess("Usuário criado com sucesso!")
setCreateForm({ email: "", password: "", roles: [] })
setIsCreateDialogOpen(false)
await fetchUsers()
} else {
const errorData = await response.json()
setError(errorData.message || "Erro ao criar usuário")
}
} catch (err:unknown) {
console.log(err)
setError("Erro de conexão com o servidor")
} finally {
setIsCreating(false)
}
}
const updateUser = async (userId: string) => {
if (!editForm.email || editForm.roles.length === 0) {
setError("Preencha todos os campos obrigatórios")
return
}
setIsUpdating(true)
setError("")
setSuccess("")
try {
const updateData: UpdateUserRequest = {
email: editForm.email,
roles: editForm.roles,
}
// Só incluir password se foi preenchido
if (editForm.password && editForm.password.trim() !== "") {
updateData.password = editForm.password
}
const response = await fetch(`${API_BASE_URL}/users/${userId}`, {
method: "PATCH",
headers: getAuthHeaders(),
body: JSON.stringify(updateData),
})
if (response.ok) {
setSuccess("Usuário atualizado com sucesso!")
setEditingUser(null)
setEditForm({ email: "", password: "", roles: [] })
await fetchUsers()
} else {
const errorData = await response.json()
setError(errorData.message || "Erro ao atualizar usuário")
}
} catch (err) {
console.log(err)
setError("Erro de conexão com o servidor")
} finally {
setIsUpdating(false)
}
}
const deleteUser = async (userId: string) => {
if (!confirm("Tem certeza que deseja excluir este usuário? Esta ação não pode ser desfeita.")) {
return
}
setIsDeleting(userId)
setError("")
setSuccess("")
try {
const response = await fetch(`${API_BASE_URL}/users/${userId}`, {
method: "DELETE",
headers: getAuthHeaders(),
})
if (response.ok) {
setSuccess("Usuário excluído com sucesso!")
await fetchUsers()
} else {
const errorData = await response.json()
setError(errorData.message || "Erro ao excluir usuário")
}
} catch (err:unknown) {
console.log(err)
setError("Erro de conexão com o servidor")
} finally {
setIsDeleting(null)
}
}
const startEditUser = (user: User) => {
setEditingUser(user._id.$oid)
setEditForm({
email: user.email,
password: "", // Deixar vazio para não alterar a senha
roles: [...user.roles],
})
}
const cancelEditUser = () => {
setEditingUser(null)
setEditForm({ email: "", password: "", roles: [] })
}
const handleEditRoleChange = (roleId: string, checked: boolean | "indeterminate") => {
if (checked === true) {
setEditForm((prev) => ({ ...prev, roles: [...prev.roles, roleId] }))
} else {
setEditForm((prev) => ({ ...prev, roles: prev.roles.filter((role) => role !== roleId) }))
}
}
const handleCreateRoleChange = (roleId: string, checked: boolean | "indeterminate") => {
if (checked === true) {
setCreateForm((prev) => ({ ...prev, roles: [...prev.roles, roleId] }))
} else {
setCreateForm((prev) => ({ ...prev, roles: prev.roles.filter((role) => role !== roleId) }))
}
}
const getRoleBadgeVariant = (role: string) => {
switch (role) {
case "admin":
return "destructive" as const
case "financeiro":
return "default" as const
case "user":
return "secondary" as const
default:
return "outline" as const
}
}
const getRoleLabel = (roleId: string) => {
const role = AVAILABLE_ROLES.find((r) => r.id === roleId)
return role ? role.label : roleId
}
const canDeleteUser = (user: User) => {
const currentUserEmail = getCurrentUserEmail()
// Não permitir que o usuário delete a si mesmo
return user.email !== currentUserEmail
}
useEffect(() => {
fetchUsers()
}, [])
// Verificar se o usuário tem permissão para gerenciar usuários
if (!isAdmin()) {
return (
<div className="flex flex-col items-center justify-center py-12 text-center">
<div className="rounded-full bg-red-100 p-6 mb-4">
<Shield className="h-8 w-8 text-red-600" />
</div>
<h3 className="text-lg font-medium mb-2">Acesso Negado</h3>
<p className="text-gray-500 max-w-md">
Você não tem permissão para acessar o gerenciamento de usuários. Entre em contato com um administrador.
</p>
</div>
)
}
return (
<div className="space-y-6">
{/* Cabeçalho com botão de criar */}
<div className="flex justify-end items-center">
<div className="flex gap-2">
<Button onClick={fetchUsers} disabled={isLoading} variant="outline">
{isLoading ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <RefreshCw className="mr-2 h-4 w-4" />}
Atualizar
</Button>
<Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
<DialogTrigger asChild>
<Button>
<Plus className="mr-2 h-4 w-4" />
Novo Usuário
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Criar Novo Usuário</DialogTitle>
<DialogDescription>Preencha as informações do novo usuário e suas permissões</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email *</Label>
<Input
id="email"
type="email"
value={createForm.email}
onChange={(e) => setCreateForm((prev) => ({ ...prev, email: e.target.value }))}
placeholder="usuario@exemplo.com"
/>
</div>
<div className="space-y-2">
<Label htmlFor="password">Senha *</Label>
<Input
id="password"
type="password"
value={createForm.password}
onChange={(e) => setCreateForm((prev) => ({ ...prev, password: e.target.value }))}
placeholder="Senha do usuário"
minLength={8}
/>
</div>
<div className="space-y-2">
<Label>Permissões *</Label>
<div className="space-y-2">
{AVAILABLE_ROLES.map((role) => (
<div key={role.id} className="flex items-center space-x-2">
<Checkbox
id={`create-${role.id}`}
checked={createForm.roles.includes(role.id)}
onCheckedChange={(checked) => handleCreateRoleChange(role.id, checked as boolean)}
/>
<div className="flex-1">
<Label htmlFor={`create-${role.id}`} className="text-sm font-medium">
{role.label}
</Label>
<p className="text-xs text-gray-500">{role.description}</p>
</div>
</div>
))}
</div>
</div>
<div className="flex gap-2 pt-4">
<Button onClick={createUser} disabled={isCreating} className="flex-1">
{isCreating ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Save className="mr-2 h-4 w-4" />}
Criar Usuário
</Button>
<Button variant="outline" onClick={() => setIsCreateDialogOpen(false)} disabled={isCreating}>
Cancelar
</Button>
</div>
</div>
</DialogContent>
</Dialog>
</div>
</div>
{error && (
<Alert variant="destructive">
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
{success && (
<Alert>
<AlertDescription>{success}</AlertDescription>
</Alert>
)}
{/* Tabela de Usuários */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Users className="h-5 w-5" />
Lista de Usuários ({users.length} usuários)
</CardTitle>
</CardHeader>
<CardContent>
{users.length > 0 ? (
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead>Email</TableHead>
<TableHead>Permissões</TableHead>
<TableHead>Ações</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.map((user) => (
<TableRow key={user._id.$oid}>
<TableCell>
{editingUser === user._id.$oid ? (
<Input
type="email"
value={editForm.email}
onChange={(e) => setEditForm((prev) => ({ ...prev, email: e.target.value }))}
placeholder="Email do usuário"
/>
) : (
<div className="font-medium">{user.email}</div>
)}
{editingUser === user._id.$oid && (
<div className="mt-2">
<Input
type="password"
value={editForm.password}
onChange={(e) => setEditForm((prev) => ({ ...prev, password: e.target.value }))}
placeholder="Nova senha (deixe vazio para não alterar)"
/>
</div>
)}
</TableCell>
<TableCell>
{editingUser === user._id.$oid ? (
<div className="space-y-2">
{AVAILABLE_ROLES.map((role) => (
<div key={role.id} className="flex items-center space-x-2">
<Checkbox
id={`edit-${role.id}-${user._id.$oid}`}
checked={editForm.roles.includes(role.id)}
onCheckedChange={(checked) => handleEditRoleChange(role.id, checked as boolean)}
/>
<Label htmlFor={`edit-${role.id}-${user._id.$oid}`} className="text-sm">
{role.label}
</Label>
</div>
))}
</div>
) : (
<div className="flex flex-wrap gap-1">
{user.roles.map((role) => (
<Badge key={role} variant={getRoleBadgeVariant(role)}>
{getRoleLabel(role)}
</Badge>
))}
</div>
)}
</TableCell>
<TableCell>
<div className="flex gap-2">
{editingUser === user._id.$oid ? (
<>
<Button size="sm" onClick={() => updateUser(user._id.$oid)} disabled={isUpdating}>
{isUpdating ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<Save className="h-4 w-4" />
)}
</Button>
<Button size="sm" variant="outline" onClick={cancelEditUser} disabled={isUpdating}>
<X className="h-4 w-4" />
</Button>
</>
) : (
<>
<Button size="sm" variant="outline" onClick={() => startEditUser(user)}>
<Edit2 className="h-4 w-4" />
</Button>
{canDeleteUser(user) && (
<Button
size="sm"
variant="outline"
onClick={() => deleteUser(user._id.$oid)}
disabled={isDeleting === user._id.$oid}
>
{isDeleting === user._id.$oid ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<Trash2 className="h-4 w-4" />
)}
</Button>
)}
</>
)}
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
) : (
<div className="text-center py-8 text-gray-500">
{isLoading ? "Carregando..." : "Nenhum usuário encontrado."}
</div>
)}
</CardContent>
</Card>
</div>
)
}