diff --git a/src/Components/SideBar/index.jsx b/src/Components/SideBar/index.jsx index c9ca1e12..548dcc6b 100644 --- a/src/Components/SideBar/index.jsx +++ b/src/Components/SideBar/index.jsx @@ -39,7 +39,7 @@ export default function SideBar() { state: { userId: user._id }, }); } else { - navigate("/perfil"); + navigate("/user"); } }; diff --git a/src/Pages/Protected/Users/userUpdate/index.css b/src/Pages/Protected/Users/userUpdate/index.css new file mode 100644 index 00000000..5f024904 --- /dev/null +++ b/src/Pages/Protected/Users/userUpdate/index.css @@ -0,0 +1,63 @@ +@media screen and (max-width: 1080px) { + .double-box-user { + display: contents; + grid-template-columns: 0.5fr 0.5fr; + } + + .double-buttons-user { + display: flex; + flex-direction: column; + gap: 15px; + margin-top: 20px; + } + + .container { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + min-height: 100vh; /* Para que o conteúdo ocupe toda a altura da tela */ + padding: 20px; /* Um pouco de padding para dar espaço interno */ + } + + .forms-container-user { + width: 100%; + max-width: 500px; /* Limite máximo da largura do formulário */ + padding: 10px; + border-radius: 10px; + } + + .password-edit { + display: flex; + flex-direction: column; + align-items: center; + gap: 15px; + margin-top: 20px; + } + + #addDependentBttn { + cursor: pointer; + } +} + +@media screen and (min-width: 1080px) { + .double-buttons-user { + display: flex; + justify-content: space-between; /* Distribui os botões igualmente no espaço disponível */ + align-items: end; /* Alinha os botões verticalmente */ + gap: 20px; + } + + .double-buttons-user button { + flex: 1; /* Faz com que os botões ocupem o espaço disponível igualmente */ + height: 40px; + } + + .forms-container-user { + padding-bottom: 40px; /* Garante um espaçamento ao final da página */ + } + + #addDependentBttn { + cursor: pointer; + } +} diff --git a/src/Pages/Protected/Users/userUpdate/index.jsx b/src/Pages/Protected/Users/userUpdate/index.jsx new file mode 100644 index 00000000..f4a87272 --- /dev/null +++ b/src/Pages/Protected/Users/userUpdate/index.jsx @@ -0,0 +1,254 @@ +import { useState, useEffect } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; +import FieldSelect from "../../../../Components/FieldSelect"; +import FieldText from "../../../../Components/FieldText"; +import Modal from "../../../../Components/Modal"; +import PrimaryButton from "../../../../Components/PrimaryButton"; +import SecondaryButton from "../../../../Components/SecondaryButton"; +import { + getRoles, + getLoggedUser, + updateLogged, + changePasswordInProfile, +} from "../../../../Services/userService"; +import "./index.css"; +import { + isValidCelular, + isValidEmail, + mascaraTelefone, +} from "../../../../Utils/validators"; + +export default function UserUpdatePage() { + const { state } = useLocation(); + const navigate = useNavigate(); + const userId = state?.userId; + const [showPasswords] = useState(false); + + const [nomeCompleto, setNomeCompleto] = useState(""); + const [celular, setCelular] = useState(""); + const [login, setLogin] = useState("Ativo"); + const [email, setEmail] = useState(""); + const [oldPassword, setOldPassword] = useState(""); + const [newPassword, setNewPassword] = useState(""); + const [confirmPassword, setConfirmNewPassword] = useState(""); + + const [perfilSelecionado, setPerfilSelecionado] = useState(""); + const [, setRoles] = useState([]); + const [showSaveModal, setShowSaveModal] = useState(false); + const [showPasswordSaveModal, setShowPasswordSaveModal] = useState(false); + const [isEmailValid, setIsEmailValid] = useState(true); + const [isCelularValid, setIsCelularValid] = useState(true); + const [isUserVisible, setIsUserVisible] = useState(true); + const [isNewPasswordValid] = useState(true); + + const passwordsMatch = newPassword === confirmPassword; + + useEffect(() => { + const loadRoles = async () => { + try { + const roles = await getRoles(); + setRoles(roles); + } catch (error) { + console.error("Erro ao carregar roles:", error); + } + }; + loadRoles(); + }, []); + + useEffect(() => { + const fetchUser = async () => { + try { + const user = await getLoggedUser(); + if (user) { + setNomeCompleto(user.name || ""); + setCelular(mascaraTelefone(user.phone || "")); + setLogin(user.status ? "Ativo" : "Inativo"); + setEmail(user.email || ""); + setPerfilSelecionado(user.role._id || ""); + } + } catch (error) { + console.error("Erro ao buscar usuário:", error); + } + }; + + fetchUser(); + }, [userId]); + + console.log(userId); + + const handleSave = async () => { + const trimmedCelular = celular.replace(/\D/g, ""); + const { isValid: isValidNumber, message: celularMessage } = + isValidCelular(trimmedCelular); + const { isValid: isValidEmailAddress, message: emailMessage } = + isValidEmail(email); + + setIsCelularValid(isValidNumber); + setIsEmailValid(isValidEmailAddress); + + if (!isValidNumber || !isValidEmailAddress) { + if (!isValidNumber) { + console.error(celularMessage); + } + if (!isValidEmailAddress) { + console.error(emailMessage); + } + return; + } + + const updatedUser = { + name: nomeCompleto, + email: email, + phone: trimmedCelular, + status: login === "Ativo", + role: perfilSelecionado, + }; + try { + await updateLogged(updatedUser); + handleSaveModal(); + } catch (error) { + console.error(`Erro ao atualizar usuário com ID ${userId}:`, error); + } + }; + + const handleSavePassword = async () => { + const updatedUserPassword = { + old_password: oldPassword, + new_password: newPassword, + }; + try { + await changePasswordInProfile(updatedUserPassword).then((data) => { + console.log("caraleo", data); + if (data && data.response.status != 200) { + alert(data.response.data.mensagem); + } + }); + handleSavePasswordModal(); + } catch (error) { + console.error( + `Erro ao atualizar senha do usuário com ID ${userId}:`, + error + ); + } + }; + + const handleChangeLogin = (event) => setLogin(event.target.value); + + const handleSaveModal = () => setShowSaveModal(true); + const handleSavePasswordModal = () => setShowPasswordSaveModal(true); + + const handleSaveCloseDialog = () => { + setShowSaveModal(false); + setShowPasswordSaveModal(false); + navigate("/user"); + }; + + const showUserDiv = () => setIsUserVisible(true); + const showPasswordDiv = () => setIsUserVisible(false); + + return ( +
+
+
+ + +
+ + {isUserVisible && ( +
+

Dados Pessoais

+
+ + setNomeCompleto(e.target.value.replace(/[^a-zA-ZÀ-ÿ\s]/g, "")) + } + /> + setEmail(e.target.value)} + /> + {!isEmailValid && ( + + )} +
+
+ setCelular(mascaraTelefone(e.target.value))} + /> + +
+ {!isCelularValid && ( + + )} + + + + + + +
+ )} + + {!isUserVisible && ( +
+

Alterar Senha

+ setOldPassword(e.target.value)} + /> + + setNewPassword(e.target.value)} + /> + {!isNewPasswordValid && ( + + )} + + setConfirmNewPassword(e.target.value)} + /> + +
+ {!passwordsMatch && confirmPassword && ( + As senhas não coincidem + )} +
+ + + + + + +
+ )} +
+
+ ); +} diff --git a/src/Pages/Protected/Users/userUpdate/index.test.jsx b/src/Pages/Protected/Users/userUpdate/index.test.jsx new file mode 100644 index 00000000..cdd5912a --- /dev/null +++ b/src/Pages/Protected/Users/userUpdate/index.test.jsx @@ -0,0 +1,192 @@ +import { describe, it, expect, beforeEach, vi, afterEach } from "vitest"; +import { render, screen, fireEvent, waitFor } from "@testing-library/react"; +import { BrowserRouter as Router, useNavigate } from "react-router-dom"; +import UserUpdatePage from "./index"; +import { + getRoles, + getUserById, + patchUserById, +} from "../../../../Services/userService"; +import "@testing-library/jest-dom"; + +vi.mock("../../../../Services/userService", () => ({ + getRoles: vi.fn(), + getUserById: vi.fn(), + deleteUserById: vi.fn(), + patchUserById: vi.fn(), +})); + +vi.mock("react-router-dom", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useLocation: () => ({ + state: { userId: "123" }, + }), + useNavigate: vi.fn(), // Mocks useNavigate + }; +}); + +vi.mock("../../../../Utils/permission", () => ({ + usePermissions: () => ({ + somePermission: true, + }), + checkAction: () => true, +})); + +describe("UserUpdatePage", () => { + const setup = () => { + localStorage.setItem("@App:user", JSON.stringify({ token: "mock-token" })); + + return render( + + + + ); + }; + + beforeEach(() => { + localStorage.setItem("@App:user", JSON.stringify({ token: "mock-token" })); + }); + + afterEach(() => { + vi.clearAllMocks(); + localStorage.clear(); + }); + + it("renders correctly", async () => { + getRoles.mockResolvedValueOnce([]); + getUserById.mockResolvedValueOnce({ + name: "", + phone: "", + status: true, + email: "", + role: "", + }); + + setup(); + + expect(screen.getByText("Visualização de usuário")).toBeInTheDocument(); + expect(screen.getByText("Dados Pessoais")).toBeInTheDocument(); + expect(screen.getByLabelText("Nome Completo")).toBeInTheDocument(); + expect(screen.getByLabelText("Celular")).toBeInTheDocument(); + expect(screen.getByLabelText("Status")).toBeInTheDocument(); + expect(screen.getByLabelText("Email")).toBeInTheDocument(); + expect(screen.getByText("Perfil")).toBeInTheDocument(); + }); + + it("validates email input", async () => { + getRoles.mockResolvedValueOnce([]); + getUserById.mockResolvedValueOnce({ + name: "", + phone: "", + status: true, + email: "", + role: "", + }); + + setup(); + + const emailInput = screen.getByLabelText("Email"); + fireEvent.change(emailInput, { target: { value: "invalid-email" } }); + + fireEvent.click(screen.getByText("Salvar")); + + await waitFor(() => { + expect(screen.getByText("*Insira um email válido")).toBeInTheDocument(); + }); + }); + + it("validates phone number input", async () => { + getRoles.mockResolvedValueOnce([]); + getUserById.mockResolvedValueOnce({ + name: "", + phone: "", + status: true, + email: "", + role: "", + }); + + setup(); + + const phoneInput = screen.getByLabelText("Celular"); + fireEvent.change(phoneInput, { target: { value: "123" } }); + + fireEvent.click(screen.getByText("Salvar")); + + await waitFor(() => { + expect( + screen.getByText( + "*Verifique se o número de celular inserido está completo" + ) + ).toBeInTheDocument(); + }); + }); + + it("saves user data correctly", () => { + getRoles.mockResolvedValueOnce([{ _id: "1", name: "Admin" }]); + getUserById.mockResolvedValueOnce({ + name: "John Doe", + phone: "1234567890", + status: true, + email: "john.doe@example.com", + role: { _id: "1" }, + }); + + setup(); + + fireEvent.change(screen.getByLabelText("Nome Completo"), { + target: { value: "Jane Doe" }, + }); + fireEvent.change(screen.getByLabelText("Celular"), { + target: { value: "0987654321" }, + }); + fireEvent.change(screen.getByLabelText("Email"), { + target: { value: "jane.doe@example.com" }, + }); + + fireEvent.click(screen.getByText("Salvar")); + + waitFor(() => { + expect(patchUserById).toHaveBeenCalledTimes(1); + expect(patchUserById).toHaveBeenCalledWith( + "123", + { + name: "Jane Doe", + email: "jane.doe@example.com", + phone: "0987654321", + status: true, + role: { _id: "1" }, + }, + "mock-token" + ); + expect(screen.getByText("Alterações Salvas")).toBeInTheDocument(); + }); + }); + + it("navigates to the contributions page when the button is clicked", async () => { + const mockNavigate = vi.fn(); + vi.mocked(useNavigate).mockReturnValue(mockNavigate); + + const user = { + name: "John Doe", + phone: "1234567890", + status: true, + email: "john.doe@example.com", + role: "1", + }; + + getRoles.mockResolvedValue([{ _id: "1", name: "Admin" }]); + getUserById.mockResolvedValue(user); + + setup(); + + await waitFor(() => { + expect(screen.getByLabelText("Nome Completo")).toHaveValue("John Doe"); + }); + + await fireEvent.click(screen.getByText("Histórico de Contribuições")); + + expect(mockNavigate).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/Pages/Protected/Users/userUpdatePage/index.jsx b/src/Pages/Protected/Users/userUpdatePage/index.jsx index 5b3a802f..3eecacf1 100644 --- a/src/Pages/Protected/Users/userUpdatePage/index.jsx +++ b/src/Pages/Protected/Users/userUpdatePage/index.jsx @@ -70,7 +70,6 @@ export default function UserUpdatePage() { } } }; - fetchUser(); }, [userId]); diff --git a/src/Routes/protectedRoutes.jsx b/src/Routes/protectedRoutes.jsx index fa87e138..9da8147a 100644 --- a/src/Routes/protectedRoutes.jsx +++ b/src/Routes/protectedRoutes.jsx @@ -6,6 +6,7 @@ import UserCreatePage from "../Pages/Protected/Users/userCreatePage"; import UserListPage from "../Pages/Protected/Users/userListPage"; import UserHubPage from "../Pages/Protected/Users/userHubPage"; import UserUpdatePage from "../Pages/Protected/Users/userUpdatePage"; +import UserUpdate from "../Pages/Protected/Users/userUpdate"; import Supplier from "../Pages/Protected/Supplier/CreateSupplier"; import ListSupplier from "../Pages/Protected/Supplier/ListSupplier"; import ViewSupplier from "../Pages/Protected/Supplier/UpdateSupplier"; @@ -79,6 +80,9 @@ const ProtectedRoutes = () => { /> } /> + + } /> + } /> { /> } /> + } /> { } }; +export const getLoggedUser = async () => { + try { + const token = getToken(); + const response = await APIUsers.get(`/user`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + return response.data; + } catch (error) { + console.error(`Erro ao buscar usuário:`, error); + } +}; + export const getUserById = async (id) => { try { const token = getToken(); @@ -124,6 +138,30 @@ export const patchUserById = async (id, updatedUser) => { } }; +export const updateLogged = async (updatedUser) => { + try { + const token = getToken(); + const response = await APIUsers.put( + `/user`, + { updatedUser }, + { + params: { + moduleName: "users", + action: "update", + }, + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); + + return response.data; + } catch (error) { + console.error(`Erro ao atualizar usuário`, error); + throw error; + } +}; + export const sendRecoveryPassword = async (email) => { try { const message = APIUsers.post(`/users/recover-password`, { @@ -172,6 +210,36 @@ export const changePasswordById = async (newPassword, id) => { } }; +// const response = await APIUsers.patch( +// `/users/patch/${id}`, +// { updatedUser }, +// { +// params: { +// userId: `${user._id}`, +// moduleName: "users", +// action: "update", +// }, +// headers: { +// Authorization: `Bearer ${token}`, +// }, +// } +// ); +export const changePasswordInProfile = async (passwords) => { + try { + await APIUsers.patch( + `/users/renew-password`, + { ...passwords }, + { + headers: { + Authorization: `Bearer ${getToken()}`, + }, + } + ); + } catch (error) { + return error; + } +}; + export const verifyToken = async (token) => { try { const response = await APIUsers.post(`/verify-token`, {