diff options
| author | zwlucas <lucas.fariamo08@gmail.com> | 2026-07-25 21:23:29 +0000 |
|---|---|---|
| committer | zwlucas <lucas.fariamo08@gmail.com> | 2026-07-25 21:23:29 +0000 |
| commit | ef55d9bb24430610b60c1b77821307cd72c08c55 (patch) | |
| tree | e6935a810b34370b0a4c821e55f6397aac50c832 /web/src/pages/UsersPage.tsx | |
| download | helpdesk-ef55d9bb24430610b60c1b77821307cd72c08c55.tar.gz helpdesk-ef55d9bb24430610b60c1b77821307cd72c08c55.zip | |
Signed-off-by: zwlucas <lucas.fariamo08@gmail.com>
Diffstat (limited to 'web/src/pages/UsersPage.tsx')
| -rw-r--r-- | web/src/pages/UsersPage.tsx | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/web/src/pages/UsersPage.tsx b/web/src/pages/UsersPage.tsx new file mode 100644 index 0000000..7406dcd --- /dev/null +++ b/web/src/pages/UsersPage.tsx @@ -0,0 +1,290 @@ +import { useState, type FormEvent } from 'react'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { api, type UserResponse } from '@/lib/api'; +import { useAuth } from '@/context/AuthContext'; +import { useToast } from '@/context/ToastContext'; +import styles from './UsersPage.module.css'; + +export default function UsersPage() { + const { user } = useAuth(); + const { addToast } = useToast(); + const queryClient = useQueryClient(); + + const [name, setName] = useState(''); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [role, setRole] = useState<'admin' | 'agent' | 'requester'>('requester'); + + const [editingUser, setEditingUser] = useState<UserResponse | null>(null); + const [editName, setEditName] = useState(''); + const [editEmail, setEditEmail] = useState(''); + const [editPassword, setEditPassword] = useState(''); + const [editRole, setEditRole] = useState<'admin' | 'agent' | 'requester'>('requester'); + const [editIsActive, setEditIsActive] = useState(true); + + const { data: users, isLoading } = useQuery({ + queryKey: ['users'], + queryFn: () => api.users.list(), + enabled: user?.role === 'admin', + }); + + const createMutation = useMutation({ + mutationFn: () => api.users.create({ name, email, password, role }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['users'] }); + setName(''); + setEmail(''); + setPassword(''); + setRole('requester'); + addToast('Usuário criado com sucesso!', 'success'); + }, + onError: (error: any) => { + addToast(`Erro ao criar usuário: ${error.message}`, 'error'); + }, + }); + + const updateMutation = useMutation({ + mutationFn: () => api.users.update(editingUser!.id, { + name: editName, + email: editEmail, + password: editPassword || undefined, + role: editRole, + isActive: editIsActive, + }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['users'] }); + setEditingUser(null); + addToast('Usuário atualizado com sucesso!', 'success'); + }, + onError: (error: any) => addToast(`Erro ao atualizar: ${error.message}`, 'error'), + }); + + const deleteMutation = useMutation({ + mutationFn: (id: number) => api.users.delete(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['users'] }); + addToast('Usuário excluído com sucesso!', 'success'); + }, + onError: (error: any) => addToast(`Erro ao excluir: ${error.message}`, 'error'), + }); + + function handleSubmit(e: FormEvent) { + e.preventDefault(); + createMutation.mutate(); + } + + function handleEditSubmit(e: FormEvent) { + e.preventDefault(); + updateMutation.mutate(); + } + + function openEdit(u: UserResponse) { + setEditingUser(u); + setEditName(u.name); + setEditEmail(u.email); + setEditPassword(''); + setEditRole(u.role); + setEditIsActive(u.isActive); + } + + if (user?.role !== 'admin') { + return ( + <div className={styles.container}> + <h2>Acesso Negado</h2> + <p>Apenas administradores podem gerenciar usuários.</p> + </div> + ); + } + + return ( + <div className={styles.container}> + <div className={styles.card}> + <h2 className={styles.title}>Criar Novo Usuário</h2> + <form className={styles.form} onSubmit={handleSubmit}> + <div className={styles.field}> + <label htmlFor="user-name">Nome</label> + <input + id="user-name" + type="text" + className="input" + value={name} + onChange={(e) => setName(e.target.value)} + required + /> + </div> + + <div className={styles.field}> + <label htmlFor="user-email">E-mail</label> + <input + id="user-email" + type="email" + className="input" + value={email} + onChange={(e) => setEmail(e.target.value)} + required + /> + </div> + + <div className={styles.field}> + <label htmlFor="user-pwd">Senha</label> + <input + id="user-pwd" + type="password" + className="input" + value={password} + onChange={(e) => setPassword(e.target.value)} + required + minLength={8} + /> + </div> + + <div className={styles.field}> + <label htmlFor="user-role">Nível de Acesso</label> + <select + id="user-role" + className="input" + value={role} + onChange={(e) => setRole(e.target.value as any)} + > + <option value="requester">Usuário Padrão (Requester)</option> + <option value="agent">Agente (Agent)</option> + <option value="admin">Administrador (Admin)</option> + </select> + </div> + + <button + type="submit" + className="btn btn--primary" + disabled={createMutation.isPending} + > + {createMutation.isPending ? 'Criando...' : 'Criar Usuário'} + </button> + </form> + </div> + + <div className={styles.card}> + <h2 className={styles.title}>Usuários Cadastrados</h2> + {isLoading ? ( + <p>Carregando...</p> + ) : ( + <div className={styles.tableWrapper}> + <table className={styles.table}> + <thead> + <tr> + <th>Nome</th> + <th>E-mail</th> + <th>Role</th> + <th>Ativo</th> + <th style={{ textAlign: 'right' }}>Ações</th> + </tr> + </thead> + <tbody> + {users?.map((u) => ( + <tr key={u.id}> + <td>{u.name}</td> + <td>{u.email}</td> + <td><span className={styles.roleBadge} data-role={u.role}>{u.role}</span></td> + <td>{u.isActive ? 'Sim' : 'Não'}</td> + <td style={{ textAlign: 'right' }}> + <button + className="btn btn--sm" + onClick={() => openEdit(u)} + style={{ marginRight: '8px', backgroundColor: '#3b82f6', color: '#ffffff', border: 'none' }} + > + Editar + </button> + <button + className="btn btn--sm btn--danger" + disabled={user?.id === u.id || deleteMutation.isPending} + onClick={() => { + if (confirm('Tem certeza que deseja excluir este usuário?')) { + deleteMutation.mutate(u.id); + } + }} + > + Excluir + </button> + </td> + </tr> + ))} + {users?.length === 0 && ( + <tr> + <td colSpan={5} className={styles.empty}>Nenhum usuário encontrado.</td> + </tr> + )} + </tbody> + </table> + </div> + )} + </div> + + {editingUser && ( + <div className={styles.modalOverlay}> + <div className={styles.modalContent}> + <h2 className={styles.title}>Editar Usuário</h2> + <form className={styles.form} onSubmit={handleEditSubmit}> + <div className={styles.field}> + <label>Nome</label> + <input + type="text" + className="input" + value={editName} + onChange={(e) => setEditName(e.target.value)} + required + /> + </div> + <div className={styles.field}> + <label>E-mail</label> + <input + type="email" + className="input" + value={editEmail} + onChange={(e) => setEditEmail(e.target.value)} + required + /> + </div> + <div className={styles.field}> + <label>Nova Senha (deixe em branco para não alterar)</label> + <input + type="password" + className="input" + value={editPassword} + onChange={(e) => setEditPassword(e.target.value)} + minLength={8} + /> + </div> + <div className={styles.field}> + <label>Nível de Acesso</label> + <select + className="input" + value={editRole} + onChange={(e) => setEditRole(e.target.value as any)} + > + <option value="requester">Usuário Padrão (Requester)</option> + <option value="agent">Agente (Agent)</option> + <option value="admin">Administrador (Admin)</option> + </select> + </div> + <div className={styles.field} style={{ flexDirection: 'row', alignItems: 'center', gap: '8px', marginTop: '8px' }}> + <input + id="user-active" + type="checkbox" + checked={editIsActive} + onChange={(e) => setEditIsActive(e.target.checked)} + /> + <label htmlFor="user-active" style={{ marginBottom: 0 }}>Usuário Ativo</label> + </div> + + <div style={{ display: 'flex', gap: '8px', marginTop: '16px', justifyContent: 'flex-end' }}> + <button type="button" className="btn" onClick={() => setEditingUser(null)}>Cancelar</button> + <button type="submit" className="btn btn--primary" disabled={updateMutation.isPending}> + {updateMutation.isPending ? 'Salvando...' : 'Salvar'} + </button> + </div> + </form> + </div> + </div> + )} + </div> + ); +} |