aboutsummaryrefslogtreecommitdiff
path: root/web/src/pages/UsersPage.tsx
diff options
context:
space:
mode:
authorzwlucas <lucas.fariamo08@gmail.com>2026-07-25 21:23:29 +0000
committerzwlucas <lucas.fariamo08@gmail.com>2026-07-25 21:23:29 +0000
commitef55d9bb24430610b60c1b77821307cd72c08c55 (patch)
treee6935a810b34370b0a4c821e55f6397aac50c832 /web/src/pages/UsersPage.tsx
downloadhelpdesk-ef55d9bb24430610b60c1b77821307cd72c08c55.tar.gz
helpdesk-ef55d9bb24430610b60c1b77821307cd72c08c55.zip
feat: upload of the projectHEADmaster
Signed-off-by: zwlucas <lucas.fariamo08@gmail.com>
Diffstat (limited to 'web/src/pages/UsersPage.tsx')
-rw-r--r--web/src/pages/UsersPage.tsx290
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>
+ );
+}