From 4bb2eeed6eaaa8b04f18d56be1c4372a09bc2abb Mon Sep 17 00:00:00 2001 From: mairess Date: Tue, 1 Oct 2024 18:12:00 -0300 Subject: [PATCH 1/7] feat: edit employee --- frontend/src/app/dashboard-employees/page.tsx | 6 +- frontend/src/app/dashboard-users/page.tsx | 2 +- frontend/src/components/AuthFooter.tsx | 2 +- .../src/components/ModalChangePassword.tsx | 2 +- .../src/components/ModalCreateEmployee.tsx | 4 +- frontend/src/components/ModalCreateUSer.tsx | 2 +- frontend/src/components/ModalEditEmployee.tsx | 205 ++++++++++++++++++ frontend/src/components/buttons/ButtonAdd.tsx | 2 +- .../src/components/buttons/ButtonEdit.tsx | 14 +- .../components/table-employees/TableRow.tsx | 13 +- .../src/components/table-user/TableRow.tsx | 2 +- frontend/src/services/editEmployee.ts | 47 ++++ frontend/src/store/editEmployeeSlice.ts | 64 ++++++ frontend/src/store/index.ts | 6 +- ...alPasswordChangeSlice.ts => modalSlice.ts} | 6 +- 15 files changed, 358 insertions(+), 19 deletions(-) create mode 100644 frontend/src/components/ModalEditEmployee.tsx create mode 100644 frontend/src/services/editEmployee.ts create mode 100644 frontend/src/store/editEmployeeSlice.ts rename frontend/src/store/{modalPasswordChangeSlice.ts => modalSlice.ts} (69%) diff --git a/frontend/src/app/dashboard-employees/page.tsx b/frontend/src/app/dashboard-employees/page.tsx index 146c905..13414d2 100644 --- a/frontend/src/app/dashboard-employees/page.tsx +++ b/frontend/src/app/dashboard-employees/page.tsx @@ -10,9 +10,11 @@ import useAuth from '../../hooks/useAuth'; import PaginationHeader from '../../components/table-employees/PaginationHeader'; import ModalCreateEmployee from '../../components/ModalCreateEmployee'; import { RootState } from '../../store'; +import ModalEditEmployee from '../../components/ModalEditEmployee'; function DashboardEmployees() { - const { isModalOpen } = useSelector((state: RootState) => state.modalPasswordChange); + const { isModalOpen } = useSelector((state: RootState) => state.modal); + const selectedEmployee = useSelector((state: RootState) => state.editEmployee.selectedEmployee); const isAuthenticated = useAuth(); if (isAuthenticated === null || !isAuthenticated) return null; @@ -23,6 +25,8 @@ function DashboardEmployees() { {isModalOpen && } + {isModalOpen && selectedEmployee && } + diff --git a/frontend/src/app/dashboard-users/page.tsx b/frontend/src/app/dashboard-users/page.tsx index 0075e71..e599a4d 100644 --- a/frontend/src/app/dashboard-users/page.tsx +++ b/frontend/src/app/dashboard-users/page.tsx @@ -12,7 +12,7 @@ import ModalCreateUser from '../../components/ModalCreateUSer'; import { RootState } from '../../store'; function DashboardUsers() { - const { isModalOpen } = useSelector((state: RootState) => state.modalPasswordChange); + const { isModalOpen } = useSelector((state: RootState) => state.modal); const isAuthenticated = useAuth(); if (isAuthenticated === null || !isAuthenticated) return null; diff --git a/frontend/src/components/AuthFooter.tsx b/frontend/src/components/AuthFooter.tsx index 383da2d..436973b 100644 --- a/frontend/src/components/AuthFooter.tsx +++ b/frontend/src/components/AuthFooter.tsx @@ -4,7 +4,7 @@ import Link from 'next/link'; import { useDispatch } from 'react-redux'; -import { openModal } from '../store/modalPasswordChangeSlice'; +import { openModal } from '../store/modalSlice'; import { AppDispatch } from '../store'; type AuthFooterProps = { diff --git a/frontend/src/components/ModalChangePassword.tsx b/frontend/src/components/ModalChangePassword.tsx index ad0c5e9..ca27994 100644 --- a/frontend/src/components/ModalChangePassword.tsx +++ b/frontend/src/components/ModalChangePassword.tsx @@ -10,7 +10,7 @@ import Button from './buttons/Button'; import { AppDispatch, RootState } from '../store'; import passwordChange from '../services/passwordChange'; import { clearError } from '../store/passwordChangeSlice'; -import { closeModal } from '../store/modalPasswordChangeSlice'; +import { closeModal } from '../store/modalSlice'; function ModalChangePassword() { const dispatch = useDispatch(); diff --git a/frontend/src/components/ModalCreateEmployee.tsx b/frontend/src/components/ModalCreateEmployee.tsx index 018bf7b..b68520a 100644 --- a/frontend/src/components/ModalCreateEmployee.tsx +++ b/frontend/src/components/ModalCreateEmployee.tsx @@ -13,14 +13,14 @@ import { AppDispatch, RootState } from '../store'; import { clearError, resetEmployee } from '../store/createEmployeeSlice'; import createEmployee from '../services/createEmployee'; import Button from './buttons/Button'; -import { closeModal } from '../store/modalPasswordChangeSlice'; +import { closeModal } from '../store/modalSlice'; import useToken from '../hooks/useToken'; function ModalCreateEmployee() { const dispatch = useDispatch(); const pathName = usePathname(); const token = useToken(); - const { isModalOpen } = useSelector((state: RootState) => state.modalPasswordChange); + const { isModalOpen } = useSelector((state: RootState) => state.modal); const { loading, employee, error } = useSelector((state: RootState) => state.createEmployee); const [formData, setFormData] = useState({ photo: '', fullName: '', position: '', admission: '', phone: '' }); diff --git a/frontend/src/components/ModalCreateUSer.tsx b/frontend/src/components/ModalCreateUSer.tsx index 4a1eea3..2efc476 100644 --- a/frontend/src/components/ModalCreateUSer.tsx +++ b/frontend/src/components/ModalCreateUSer.tsx @@ -12,7 +12,7 @@ import { AppDispatch, RootState } from '../store'; import { clearError, resetUser } from '../store/registerSlice'; import register from '../services/register'; import Button from './buttons/Button'; -import { closeModal } from '../store/modalPasswordChangeSlice'; +import { closeModal } from '../store/modalSlice'; function ModalCreateUser() { const dispatch = useDispatch(); diff --git a/frontend/src/components/ModalEditEmployee.tsx b/frontend/src/components/ModalEditEmployee.tsx new file mode 100644 index 0000000..e216098 --- /dev/null +++ b/frontend/src/components/ModalEditEmployee.tsx @@ -0,0 +1,205 @@ +/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +/* eslint-disable max-len */ + +import { useDispatch, useSelector } from 'react-redux'; +import { FaTimes } from 'react-icons/fa'; +import { useEffect, useState } from 'react'; +import { usePathname } from 'next/navigation'; +import Swal from 'sweetalert2'; +import Input from './Input'; +import { AppDispatch, RootState } from '../store'; +import { clearError, resetEmployee } from '../store/editEmployeeSlice'; +import editEmployee from '../services/editEmployee'; +import Button from './buttons/Button'; +import { closeModal } from '../store/modalSlice'; +import useToken from '../hooks/useToken'; +import { EmployeeType } from '../types'; +import findAllEmployees from '../services/findAllEmployees'; + +type ModalEditEmployeeProps = { + employee: EmployeeType +}; + +function ModalEditEmployee({ employee }: ModalEditEmployeeProps) { + const dispatch = useDispatch(); + const pathName = usePathname(); + const token = useToken(); + const { isModalOpen } = useSelector((state: RootState) => state.modal); + const { loading, error } = useSelector((state: RootState) => state.editEmployee); + const [formData, setFormData] = useState({ photo: '', fullName: '', position: '', admission: '', phone: '' }); + const { pageSize, pageNumber } = useSelector((state: RootState) => state.pagination); + const { column, direction } = useSelector((state: RootState) => state.sort); + const { term } = useSelector((state: RootState) => state.searchTerm); + + const { id, photo, fullName, position, admission, phone } = employee; + const idSting = id?.toString() || ''; + + useEffect(() => { + if (employee) { + setFormData({ + photo, + fullName, + position, + admission, + phone, + }); + } + }, [employee, id, photo, fullName, position, admission, phone]); + + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + dispatch(closeModal()); + setFormData({ photo: '', fullName: '', position: '', admission: '', phone: '' }); + dispatch(clearError()); + } + }; + + if (isModalOpen) { + window.addEventListener('keydown', handleKeyDown); + } + + return () => { + window.removeEventListener('keydown', handleKeyDown); + }; + }, [isModalOpen, dispatch, pathName]); + + const handleInputChange = (event: React.ChangeEvent) => { + const { name, value } = event.target; + setFormData((prevData) => ({ ...prevData, [name]: value })); + }; + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + + const resultAction = await dispatch(editEmployee({ token, id: idSting, employeeData: formData })); + + if (editEmployee.fulfilled.match(resultAction)) { + const Toast = Swal.mixin({ + toast: true, + position: 'top-end', + showConfirmButton: false, + timer: 3000, + timerProgressBar: true, + didOpen: (toast) => { + toast.onmouseenter = Swal.stopTimer; + toast.onmouseleave = Swal.resumeTimer; + }, + }); + + Toast.fire({ + icon: 'success', + title: 'Employee created successfully', + }).then(() => { + dispatch(closeModal()); + dispatch(resetEmployee()); + if (token) { dispatch(findAllEmployees({ token, pageNumber, pageSize, column, direction, term })); } + }); + } + }; + + const handleCloseModal = () => { + setFormData({ photo: '', fullName: '', position: '', admission: '', phone: '' }); + dispatch(closeModal()); + dispatch(clearError()); + }; + + const handleClickInside = (event: React.MouseEvent) => { + event.stopPropagation(); + }; + + return ( +
+ +
+ + + +

+ Edit employee +

+ + + + + + + + + + + +
+ ); +} + +export default ModalEditEmployee; diff --git a/frontend/src/components/buttons/ButtonAdd.tsx b/frontend/src/components/buttons/ButtonAdd.tsx index 73ca7fd..c84184e 100644 --- a/frontend/src/components/buttons/ButtonAdd.tsx +++ b/frontend/src/components/buttons/ButtonAdd.tsx @@ -2,7 +2,7 @@ import Image from 'next/image'; import { useDispatch } from 'react-redux'; import add from '../../../public/add.svg'; -import { openModal } from '../../store/modalPasswordChangeSlice'; +import { openModal } from '../../store/modalSlice'; import { AppDispatch } from '../../store'; function ButtonAdd() { diff --git a/frontend/src/components/buttons/ButtonEdit.tsx b/frontend/src/components/buttons/ButtonEdit.tsx index 6a94845..9b075f9 100644 --- a/frontend/src/components/buttons/ButtonEdit.tsx +++ b/frontend/src/components/buttons/ButtonEdit.tsx @@ -1,11 +1,21 @@ import Image from 'next/image'; import iconEdit from '../../../public/iconEdit.svg'; -function ButtonEdit() { +type ButtonEditProps = { + onClick?: () => void; +}; + +function ButtonEdit({ onClick = () => {} }: ButtonEditProps) { return ( - + ); } diff --git a/frontend/src/components/table-employees/TableRow.tsx b/frontend/src/components/table-employees/TableRow.tsx index 07affc6..55b54f4 100644 --- a/frontend/src/components/table-employees/TableRow.tsx +++ b/frontend/src/components/table-employees/TableRow.tsx @@ -17,6 +17,8 @@ import ButtonDelete from '../buttons/ButtonDelete'; import ButtonEdit from '../buttons/ButtonEdit'; import { AppDispatch, RootState } from '../../store'; import { setColumn, setDirection } from '../../store/sortSlice'; +import { openModal } from '../../store/modalSlice'; +import { setSelectedEmployee } from '../../store/editEmployeeSlice'; type TableRowEmployeesProps = { employee: EmployeeType @@ -49,6 +51,11 @@ function TableRowEmployees({ employee }: TableRowEmployeesProps) { setShowDetails(showDetails === 'hidden' ? '' : 'hidden'); }; + const handleEditClick = () => { + dispatch(setSelectedEmployee(employee)); + dispatch(openModal()); + }; + return ( <> @@ -72,7 +79,7 @@ function TableRowEmployees({ employee }: TableRowEmployeesProps) { {isAdmin && ( - + @@ -83,7 +90,7 @@ function TableRowEmployees({ employee }: TableRowEmployeesProps) { @@ -120,7 +127,7 @@ function TableRowEmployees({ employee }: TableRowEmployeesProps) { {isAdmin && (
- +
)} diff --git a/frontend/src/components/table-user/TableRow.tsx b/frontend/src/components/table-user/TableRow.tsx index 4fc8f73..94ff676 100644 --- a/frontend/src/components/table-user/TableRow.tsx +++ b/frontend/src/components/table-user/TableRow.tsx @@ -79,7 +79,7 @@ function TableRowUsers({ user }: TableRowUsersProps) { diff --git a/frontend/src/services/editEmployee.ts b/frontend/src/services/editEmployee.ts new file mode 100644 index 0000000..d78e641 --- /dev/null +++ b/frontend/src/services/editEmployee.ts @@ -0,0 +1,47 @@ +import { createAsyncThunk } from '@reduxjs/toolkit'; + +/* eslint-disable max-len */ + +type EditEmployeeParams = { + token: string | null; + id: string; + employeeData: { + photo: string; + fullName: string; + position: string; + admission: string; + phone: string; + }; +}; + +const editEmployee = createAsyncThunk( + 'editEmployee', + async ({ employeeData, token, id }: EditEmployeeParams, { rejectWithValue }) => { + try { + const response = await fetch(`http://localhost:8080/employees/${id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify(employeeData), + }); + + if (!response.ok) { + const errorData = await response.json(); + console.error('Error fetching:', errorData.message); + return rejectWithValue(errorData.message); + } + + const employee = await response.json(); + + return employee; + } catch (error) { + console.error('Error fetching:', error); + return rejectWithValue('Something went wrong.'); + } + }, + +); + +export default editEmployee; diff --git a/frontend/src/store/editEmployeeSlice.ts b/frontend/src/store/editEmployeeSlice.ts new file mode 100644 index 0000000..06a3d70 --- /dev/null +++ b/frontend/src/store/editEmployeeSlice.ts @@ -0,0 +1,64 @@ +/* eslint-disable max-len */ +import { createSlice } from '@reduxjs/toolkit'; +import { EmployeeType } from '../types'; +import editEmployee from '../services/editEmployee'; + +type EditEmployeeState = { + employee: EmployeeType; + selectedEmployee: EmployeeType | null; + loading: boolean; + error: string | null +}; + +const initialEmployee = { + id: null, + photo: '', + fullName: '', + position: '', + admission: '', + phone: '', +}; + +const initialState: EditEmployeeState = { + employee: initialEmployee, + selectedEmployee: null, + loading: false, + error: null, +}; + +const editEmployeeSlice = createSlice({ + name: 'editEmployee', + initialState, + reducers: { + clearError: (state) => { + state.error = null; + }, + resetEmployee: (state) => { + state.employee = initialEmployee; + }, + setSelectedEmployee: (state, action) => { + state.selectedEmployee = action.payload; + }, + clearSelectedEmployee: (state) => { + state.selectedEmployee = null; + }, + }, + extraReducers: (builder) => { + builder + .addCase(editEmployee.pending, (state) => { + state.loading = true; + state.error = null; + }) + .addCase(editEmployee.fulfilled, (state, action) => { + state.loading = false; + state.employee = action.payload; + }) + .addCase(editEmployee.rejected, (state, action) => { + state.loading = false; + state.error = action.payload as string; + }); + }, +}); + +export const { clearError, resetEmployee, setSelectedEmployee, clearSelectedEmployee } = editEmployeeSlice.actions; +export default editEmployeeSlice.reducer; diff --git a/frontend/src/store/index.ts b/frontend/src/store/index.ts index f9d4c67..c7e32af 100644 --- a/frontend/src/store/index.ts +++ b/frontend/src/store/index.ts @@ -4,11 +4,12 @@ import registerReducer from './registerSlice'; import findAllUsersReducer from './findAllUsersSlice'; import findAllEmployeesReducer from './findAllEmployeesSlice'; import passwordChangeReducer from './passwordChangeSlice'; -import modalPasswordChangeReducer from './modalPasswordChangeSlice'; +import modalReducer from './modalSlice'; import paginationReducer from './paginationSlice'; import sortReducer from './sortSlice'; import searchTermReducer from './searchTermSlice'; import createEmployeeReducer from './createEmployeeSlice'; +import editEmployeeReducer from './editEmployeeSlice'; import findLoggedUserReducer from './findLoggedUserSlice'; const store = configureStore({ @@ -19,11 +20,12 @@ const store = configureStore({ findAllEmployees: findAllEmployeesReducer, findLoggedUser: findLoggedUserReducer, passwordChange: passwordChangeReducer, - modalPasswordChange: modalPasswordChangeReducer, + modal: modalReducer, pagination: paginationReducer, sort: sortReducer, searchTerm: searchTermReducer, createEmployee: createEmployeeReducer, + editEmployee: editEmployeeReducer, }, }); diff --git a/frontend/src/store/modalPasswordChangeSlice.ts b/frontend/src/store/modalSlice.ts similarity index 69% rename from frontend/src/store/modalPasswordChangeSlice.ts rename to frontend/src/store/modalSlice.ts index 8b38420..cecdad8 100644 --- a/frontend/src/store/modalPasswordChangeSlice.ts +++ b/frontend/src/store/modalSlice.ts @@ -8,7 +8,7 @@ const initialState: ModalPasswordChangeState = { isModalOpen: false, }; -const modalPasswordChangeSlice = createSlice({ +const modalSlice = createSlice({ name: 'modalPasswordChange', initialState, reducers: { @@ -21,5 +21,5 @@ const modalPasswordChangeSlice = createSlice({ }, }); -export const { openModal, closeModal } = modalPasswordChangeSlice.actions; -export default modalPasswordChangeSlice.reducer; +export const { openModal, closeModal } = modalSlice.actions; +export default modalSlice.reducer; From d1fb8293b10e5f9fdbab170bb3383f2c5a803db8 Mon Sep 17 00:00:00 2001 From: mairess Date: Wed, 2 Oct 2024 12:10:03 -0300 Subject: [PATCH 2/7] refactor: add UserUpdateDto --- .../employee/controller/UserController.java | 9 +-- .../controller/dto/UserUpdateDto.java | 64 +++++++++++++++++++ .../maires/employee/service/UserService.java | 10 +-- 3 files changed, 74 insertions(+), 9 deletions(-) create mode 100644 backend/src/main/java/org/maires/employee/controller/dto/UserUpdateDto.java diff --git a/backend/src/main/java/org/maires/employee/controller/UserController.java b/backend/src/main/java/org/maires/employee/controller/UserController.java index 9a56907..155b1c9 100644 --- a/backend/src/main/java/org/maires/employee/controller/UserController.java +++ b/backend/src/main/java/org/maires/employee/controller/UserController.java @@ -7,6 +7,7 @@ import java.util.Map; import org.maires.employee.controller.dto.UserCreationDto; import org.maires.employee.controller.dto.UserDto; +import org.maires.employee.controller.dto.UserUpdateDto; import org.maires.employee.entity.User; import org.maires.employee.service.UserService; import org.maires.employee.service.exception.UserNotFoundException; @@ -134,8 +135,8 @@ public ResponseEntity create(@Valid @RequestBody UserCreationDto userCr /** * Update response entity. * - * @param userId the user id - * @param userCreationDto the user creation dto + * @param userId the user id + * @param userUpdateDto the user update dto * @return the response entity * @throws JsonMappingException the json mapping exception * @throws UserNotFoundException the user not found exception @@ -144,10 +145,10 @@ public ResponseEntity create(@Valid @RequestBody UserCreationDto userCr @PreAuthorize("hasAnyAuthority('ADMIN')") public ResponseEntity update( @PathVariable Long userId, - @Valid @RequestBody UserCreationDto userCreationDto + @Valid @RequestBody UserUpdateDto userUpdateDto ) throws JsonMappingException, UserNotFoundException { - User userUpdated = userService.update(userId, userCreationDto); + User userUpdated = userService.update(userId, userUpdateDto); return ResponseEntity.status(HttpStatus.OK).body(UserDto.fromEntity(userUpdated)); diff --git a/backend/src/main/java/org/maires/employee/controller/dto/UserUpdateDto.java b/backend/src/main/java/org/maires/employee/controller/dto/UserUpdateDto.java new file mode 100644 index 0000000..39eccd4 --- /dev/null +++ b/backend/src/main/java/org/maires/employee/controller/dto/UserUpdateDto.java @@ -0,0 +1,64 @@ +package org.maires.employee.controller.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import org.maires.employee.entity.User; +import org.maires.employee.security.Role; +import org.maires.employee.validation.EnumValidator; + + +/** + * The type User update dto. + */ +public record UserUpdateDto( + @Pattern( + regexp = "$|^(https?)://.*\\.(jpg|jpeg|png|gif|bmp|webp)$", + message = "Invalid URL format" + ) + String photo, + + @NotNull(message = "Full name cannot be null!") + @NotBlank(message = "Full name cannot be blank!") + @Size(min = 4, message = "Full name must be >= 4 characters!") + @Pattern( + regexp = "^[^0-9]*$", + message = "Full name must not contain digit!" + ) + String fullName, + + @NotNull(message = "Username cannot be null!") + @NotBlank(message = "Username cannot be blank!") + @Size(min = 3, message = "Username must be >= 3 characters!") + String username, + + @NotNull(message = "Email cannot be null!") + @NotBlank(message = "Email cannot be blank!") + @Email(message = "Email must be a valid email address!") + String email, + + @NotNull(message = "Role cannot be null! Try ADMIN or USER") + @EnumValidator(enumClazz = Role.class, message = "Role must be ADMIN or USER") + String role +) { + + + /** + * To entity user. + * + * @param existingUser the existing user + * @return the user + */ + public User toEntity(User existingUser) { + existingUser.setPhoto(photo); + existingUser.setFullName(fullName); + existingUser.setUsername(username); + existingUser.setEmail(email); + existingUser.setRole(Role.valueOf(role.toUpperCase())); + + return existingUser; + } + +} \ No newline at end of file diff --git a/backend/src/main/java/org/maires/employee/service/UserService.java b/backend/src/main/java/org/maires/employee/service/UserService.java index 7edc7de..b10a20f 100644 --- a/backend/src/main/java/org/maires/employee/service/UserService.java +++ b/backend/src/main/java/org/maires/employee/service/UserService.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.transaction.Transactional; import java.util.Map; -import org.maires.employee.controller.dto.UserCreationDto; +import org.maires.employee.controller.dto.UserUpdateDto; import org.maires.employee.entity.User; import org.maires.employee.repository.UserRepository; import org.maires.employee.repository.specification.UserSpecification; @@ -120,18 +120,18 @@ public User create(User user) { /** * Update user. * - * @param userId the user id - * @param userCreationDto the user creation dto + * @param userId the user id + * @param userUpdateDto the user update dto * @return the user * @throws UserNotFoundException the user not found exception * @throws JsonMappingException the json mapping exception */ @Transactional - public User update(Long userId, UserCreationDto userCreationDto) + public User update(Long userId, UserUpdateDto userUpdateDto) throws UserNotFoundException, JsonMappingException { User userToUpdate = findById(userId); - objectMapper.updateValue(userToUpdate, userCreationDto); + objectMapper.updateValue(userToUpdate, userUpdateDto); return userRepository.save(userToUpdate); } From bd7fb8784e73ef9e8a7c7c9a1fdceb6915eb9eb8 Mon Sep 17 00:00:00 2001 From: mairess Date: Wed, 2 Oct 2024 12:12:46 -0300 Subject: [PATCH 3/7] feat: edit user and employee --- frontend/src/app/dashboard-employees/page.tsx | 11 +- frontend/src/app/dashboard-users/page.tsx | 11 +- frontend/src/components/AuthFooter.tsx | 4 +- frontend/src/components/FormLogin.tsx | 6 +- frontend/src/components/buttons/ButtonAdd.tsx | 8 +- .../{ => modal}/ModalChangePassword.tsx | 22 +- .../{ => modal}/ModalCreateEmployee.tsx | 28 +-- .../{ => modal}/ModalCreateUSer.tsx | 24 +- .../{ => modal}/ModalEditEmployee.tsx | 32 +-- .../src/components/modal/ModalEditUser.tsx | 205 ++++++++++++++++++ .../components/table-employees/TableRow.tsx | 10 +- .../src/components/table-user/TableRow.tsx | 11 +- frontend/src/services/editUser.ts | 47 ++++ frontend/src/store/editUserSlice.ts | 65 ++++++ frontend/src/store/findLoggedUserSlice.ts | 2 +- frontend/src/store/index.ts | 14 +- .../src/store/modalCreateEmployeeSlice.ts | 27 +++ frontend/src/store/modalCreateUserSlice.ts | 27 +++ frontend/src/store/modalEditEmployeeSlice.ts | 27 +++ frontend/src/store/modalEditUserSlice.ts | 27 +++ .../src/store/modalPasswordChangeSlice.ts | 26 +++ frontend/src/store/modalSlice.ts | 25 --- 22 files changed, 556 insertions(+), 103 deletions(-) rename frontend/src/components/{ => modal}/ModalChangePassword.tsx (82%) rename frontend/src/components/{ => modal}/ModalCreateEmployee.tsx (85%) rename frontend/src/components/{ => modal}/ModalCreateUSer.tsx (89%) rename frontend/src/components/{ => modal}/ModalEditEmployee.tsx (86%) create mode 100644 frontend/src/components/modal/ModalEditUser.tsx create mode 100644 frontend/src/services/editUser.ts create mode 100644 frontend/src/store/editUserSlice.ts create mode 100644 frontend/src/store/modalCreateEmployeeSlice.ts create mode 100644 frontend/src/store/modalCreateUserSlice.ts create mode 100644 frontend/src/store/modalEditEmployeeSlice.ts create mode 100644 frontend/src/store/modalEditUserSlice.ts create mode 100644 frontend/src/store/modalPasswordChangeSlice.ts delete mode 100644 frontend/src/store/modalSlice.ts diff --git a/frontend/src/app/dashboard-employees/page.tsx b/frontend/src/app/dashboard-employees/page.tsx index 13414d2..91fe327 100644 --- a/frontend/src/app/dashboard-employees/page.tsx +++ b/frontend/src/app/dashboard-employees/page.tsx @@ -8,12 +8,13 @@ import SearchBar from '../../components/SearchBar'; import Table from '../../components/table-employees/Table'; import useAuth from '../../hooks/useAuth'; import PaginationHeader from '../../components/table-employees/PaginationHeader'; -import ModalCreateEmployee from '../../components/ModalCreateEmployee'; +import ModalCreateEmployee from '../../components/modal/ModalCreateEmployee'; import { RootState } from '../../store'; -import ModalEditEmployee from '../../components/ModalEditEmployee'; +import ModalEditEmployee from '../../components/modal/ModalEditEmployee'; function DashboardEmployees() { - const { isModalOpen } = useSelector((state: RootState) => state.modal); + const { isModalCreateEmployeeOpen } = useSelector((state: RootState) => state.modalCreateEmployee); + const { isModalOpenEditEmployee } = useSelector((state: RootState) => state.modalEditEmployee); const selectedEmployee = useSelector((state: RootState) => state.editEmployee.selectedEmployee); const isAuthenticated = useAuth(); @@ -23,9 +24,9 @@ function DashboardEmployees() {
- {isModalOpen && } + {isModalCreateEmployeeOpen && } - {isModalOpen && selectedEmployee && } + {isModalOpenEditEmployee && selectedEmployee && } diff --git a/frontend/src/app/dashboard-users/page.tsx b/frontend/src/app/dashboard-users/page.tsx index e599a4d..16deb7b 100644 --- a/frontend/src/app/dashboard-users/page.tsx +++ b/frontend/src/app/dashboard-users/page.tsx @@ -8,11 +8,14 @@ import SearchBar from '../../components/SearchBar'; import Table from '../../components/table-user/Table'; import useAuth from '../../hooks/useAuth'; import PaginationHeader from '../../components/table-user/PaginationHeader'; -import ModalCreateUser from '../../components/ModalCreateUSer'; +import ModalCreateUser from '../../components/modal/ModalCreateUSer'; import { RootState } from '../../store'; +import ModalEditUser from '../../components/modal/ModalEditUser'; function DashboardUsers() { - const { isModalOpen } = useSelector((state: RootState) => state.modal); + const { isModalCreateUserOpen } = useSelector((state: RootState) => state.modalCreateUser); + const { isModalEditUserOpen } = useSelector((state: RootState) => state.modalEditUser); + const selectedUser = useSelector((state: RootState) => state.editUser.selectedUser); const isAuthenticated = useAuth(); if (isAuthenticated === null || !isAuthenticated) return null; @@ -20,7 +23,9 @@ function DashboardUsers() { return (
- {isModalOpen && } + {isModalCreateUserOpen && } + + {isModalEditUserOpen && selectedUser && } diff --git a/frontend/src/components/AuthFooter.tsx b/frontend/src/components/AuthFooter.tsx index 436973b..73d52cb 100644 --- a/frontend/src/components/AuthFooter.tsx +++ b/frontend/src/components/AuthFooter.tsx @@ -4,7 +4,7 @@ import Link from 'next/link'; import { useDispatch } from 'react-redux'; -import { openModal } from '../store/modalSlice'; +import { openModalPasswordChange } from '../store/modalPasswordChangeSlice'; import { AppDispatch } from '../store'; type AuthFooterProps = { @@ -19,7 +19,7 @@ function AuthFooter({ forgotPassword = null, doNotHaveAccountText, doNotHaveAcco const handleOpenModal = (event: React.MouseEvent) => { event.preventDefault(); - dispatch(openModal()); + dispatch(openModalPasswordChange()); }; return ( diff --git a/frontend/src/components/FormLogin.tsx b/frontend/src/components/FormLogin.tsx index b6c9880..9e0ed36 100644 --- a/frontend/src/components/FormLogin.tsx +++ b/frontend/src/components/FormLogin.tsx @@ -11,7 +11,7 @@ import Button from './buttons/Button'; import Divider from './Divider'; import Input from './Input'; import KeepLogged from './KeepLogged'; -import ModalChangePassword from './ModalChangePassword'; +import ModalChangePassword from './modal/ModalChangePassword'; import { AppDispatch, RootState } from '../store'; import auth from '../services/auth'; import { clearError } from '../store/authSlice'; @@ -19,7 +19,7 @@ import { clearError } from '../store/authSlice'; function FormLogin() { const dispatch = useDispatch(); const { token, loading, error } = useSelector((state: RootState) => state.auth); - const { isModalOpen } = useSelector((state: RootState) => state.modalPasswordChange); + const { isModalPasswordChangeOpen } = useSelector((state: RootState) => state.modalPasswordChange); const [formData, setFormaData] = useState({ username: '', password: '' }); const [isLoaded, setIsLoaded] = useState(false); const [keepLogged, setKeepLogged] = useState(false); @@ -80,7 +80,7 @@ function FormLogin() { <> - {isModalOpen && } + {isModalPasswordChangeOpen && }
(); + const pathName = usePathname(); const handleOpenModal = (event: React.MouseEvent) => { event.preventDefault(); - dispatch(openModal()); + const openModal = pathName === '/dashboard-employees' ? openModalCreateEmployee() : openModalCreateUser(); + dispatch(openModal); }; return ( diff --git a/frontend/src/components/ModalChangePassword.tsx b/frontend/src/components/modal/ModalChangePassword.tsx similarity index 82% rename from frontend/src/components/ModalChangePassword.tsx rename to frontend/src/components/modal/ModalChangePassword.tsx index ca27994..0485153 100644 --- a/frontend/src/components/ModalChangePassword.tsx +++ b/frontend/src/components/modal/ModalChangePassword.tsx @@ -5,36 +5,36 @@ import { useEffect, useState } from 'react'; import { FaTimes } from 'react-icons/fa'; import { useDispatch, useSelector } from 'react-redux'; -import Input from './Input'; -import Button from './buttons/Button'; -import { AppDispatch, RootState } from '../store'; -import passwordChange from '../services/passwordChange'; -import { clearError } from '../store/passwordChangeSlice'; -import { closeModal } from '../store/modalSlice'; +import Input from '../Input'; +import Button from '../buttons/Button'; +import { AppDispatch, RootState } from '../../store'; +import passwordChange from '../../services/passwordChange'; +import { clearError } from '../../store/passwordChangeSlice'; +import { closeModalPasswordChange } from '../../store/modalPasswordChangeSlice'; function ModalChangePassword() { const dispatch = useDispatch(); - const { isModalOpen } = useSelector((state: RootState) => state.modalPasswordChange); + const { isModalPasswordChangeOpen } = useSelector((state: RootState) => state.modalPasswordChange); const { loading, message, error } = useSelector((state: RootState) => state.passwordChange); const [formData, setFormData] = useState({ email: '' }); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') { - dispatch(closeModal()); + dispatch(closeModalPasswordChange()); setFormData({ email: '' }); dispatch(clearError()); } }; - if (isModalOpen) { + if (isModalPasswordChangeOpen) { window.addEventListener('keydown', handleKeyDown); } return () => { window.removeEventListener('keydown', handleKeyDown); }; - }, [isModalOpen, dispatch]); + }, [isModalPasswordChangeOpen, dispatch]); const isFormValid = !formData.email; @@ -44,7 +44,7 @@ function ModalChangePassword() { const handleCloseModal = () => { setFormData({ email: '' }); - dispatch(closeModal()); + dispatch(closeModalPasswordChange()); dispatch(clearError()); }; diff --git a/frontend/src/components/ModalCreateEmployee.tsx b/frontend/src/components/modal/ModalCreateEmployee.tsx similarity index 85% rename from frontend/src/components/ModalCreateEmployee.tsx rename to frontend/src/components/modal/ModalCreateEmployee.tsx index b68520a..5a49765 100644 --- a/frontend/src/components/ModalCreateEmployee.tsx +++ b/frontend/src/components/modal/ModalCreateEmployee.tsx @@ -8,39 +8,39 @@ import { FaTimes } from 'react-icons/fa'; import { useEffect, useState } from 'react'; import { usePathname } from 'next/navigation'; import Swal from 'sweetalert2'; -import Input from './Input'; -import { AppDispatch, RootState } from '../store'; -import { clearError, resetEmployee } from '../store/createEmployeeSlice'; -import createEmployee from '../services/createEmployee'; -import Button from './buttons/Button'; -import { closeModal } from '../store/modalSlice'; -import useToken from '../hooks/useToken'; +import Input from '../Input'; +import { AppDispatch, RootState } from '../../store'; +import { clearError, resetEmployee } from '../../store/createEmployeeSlice'; +import createEmployee from '../../services/createEmployee'; +import Button from '../buttons/Button'; +import { closeModalCreateEmployee } from '../../store/modalCreateEmployeeSlice'; +import useToken from '../../hooks/useToken'; function ModalCreateEmployee() { const dispatch = useDispatch(); const pathName = usePathname(); const token = useToken(); - const { isModalOpen } = useSelector((state: RootState) => state.modal); + const { isModalCreateEmployeeOpen } = useSelector((state: RootState) => state.modalCreateEmployee); const { loading, employee, error } = useSelector((state: RootState) => state.createEmployee); const [formData, setFormData] = useState({ photo: '', fullName: '', position: '', admission: '', phone: '' }); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') { - dispatch(closeModal()); + dispatch(closeModalCreateEmployee()); setFormData({ photo: '', fullName: '', position: '', admission: '', phone: '' }); dispatch(clearError()); } }; - if (isModalOpen) { + if (isModalCreateEmployeeOpen) { window.addEventListener('keydown', handleKeyDown); } return () => { window.removeEventListener('keydown', handleKeyDown); }; - }, [isModalOpen, dispatch, pathName]); + }, [isModalCreateEmployeeOpen, dispatch, pathName]); useEffect(() => { if (employee.id) { @@ -60,7 +60,7 @@ function ModalCreateEmployee() { icon: 'success', title: 'Employee created successfully', }).then(() => { - dispatch(closeModal()); + dispatch(closeModalCreateEmployee()); dispatch(resetEmployee()); }); } @@ -76,13 +76,13 @@ function ModalCreateEmployee() { dispatch(createEmployee({ employeeData: formData, token })); if (employee.id) { - dispatch(closeModal()); + dispatch(closeModalCreateEmployee()); } }; const handleCloseModal = () => { setFormData({ photo: '', fullName: '', position: '', admission: '', phone: '' }); - dispatch(closeModal()); + dispatch(closeModalCreateEmployee()); dispatch(clearError()); }; diff --git a/frontend/src/components/ModalCreateUSer.tsx b/frontend/src/components/modal/ModalCreateUSer.tsx similarity index 89% rename from frontend/src/components/ModalCreateUSer.tsx rename to frontend/src/components/modal/ModalCreateUSer.tsx index 2efc476..20ab471 100644 --- a/frontend/src/components/ModalCreateUSer.tsx +++ b/frontend/src/components/modal/ModalCreateUSer.tsx @@ -7,16 +7,16 @@ import { useDispatch, useSelector } from 'react-redux'; import { FaTimes } from 'react-icons/fa'; import { useEffect, useState } from 'react'; import Swal from 'sweetalert2'; -import Input from './Input'; -import { AppDispatch, RootState } from '../store'; -import { clearError, resetUser } from '../store/registerSlice'; -import register from '../services/register'; -import Button from './buttons/Button'; -import { closeModal } from '../store/modalSlice'; +import Input from '../Input'; +import { AppDispatch, RootState } from '../../store'; +import { clearError, resetUser } from '../../store/registerSlice'; +import register from '../../services/register'; +import Button from '../buttons/Button'; +import { closeModalCreateUser } from '../../store/modalCreateUserSlice'; function ModalCreateUser() { const dispatch = useDispatch(); - const { isModalOpen } = useSelector((state: RootState) => state.modalPasswordChange); + const { isModalCreateUserOpen } = useSelector((state: RootState) => state.modalCreateUser); const { loading, user, error } = useSelector((state: RootState) => state.register); const [formData, setFormData] = useState({ photo: '', fullName: '', username: '', email: '', password: '', role: '' }); const [confirmPassword, setConfirmPassword] = useState(''); @@ -24,20 +24,20 @@ function ModalCreateUser() { useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') { - dispatch(closeModal()); + dispatch(closeModalCreateUser()); setFormData({ photo: '', fullName: '', username: '', email: '', password: '', role: '' }); dispatch(clearError()); } }; - if (isModalOpen) { + if (isModalCreateUserOpen) { window.addEventListener('keydown', handleKeyDown); } return () => { window.removeEventListener('keydown', handleKeyDown); }; - }, [isModalOpen, dispatch]); + }, [isModalCreateUserOpen, dispatch]); useEffect(() => { if (user.id) { @@ -57,7 +57,7 @@ function ModalCreateUser() { icon: 'success', title: 'User created successfully', }).then(() => { - dispatch(closeModal()); + dispatch(closeModalCreateUser()); dispatch(resetUser()); }); } @@ -83,7 +83,7 @@ function ModalCreateUser() { const handleCloseModal = () => { setFormData({ photo: '', fullName: '', username: '', email: '', password: '', role: '' }); - dispatch(closeModal()); + dispatch(closeModalCreateUser()); dispatch(clearError()); }; diff --git a/frontend/src/components/ModalEditEmployee.tsx b/frontend/src/components/modal/ModalEditEmployee.tsx similarity index 86% rename from frontend/src/components/ModalEditEmployee.tsx rename to frontend/src/components/modal/ModalEditEmployee.tsx index e216098..fe665ab 100644 --- a/frontend/src/components/ModalEditEmployee.tsx +++ b/frontend/src/components/modal/ModalEditEmployee.tsx @@ -8,15 +8,15 @@ import { FaTimes } from 'react-icons/fa'; import { useEffect, useState } from 'react'; import { usePathname } from 'next/navigation'; import Swal from 'sweetalert2'; -import Input from './Input'; -import { AppDispatch, RootState } from '../store'; -import { clearError, resetEmployee } from '../store/editEmployeeSlice'; -import editEmployee from '../services/editEmployee'; -import Button from './buttons/Button'; -import { closeModal } from '../store/modalSlice'; -import useToken from '../hooks/useToken'; -import { EmployeeType } from '../types'; -import findAllEmployees from '../services/findAllEmployees'; +import Input from '../Input'; +import { AppDispatch, RootState } from '../../store'; +import { clearError, resetEmployee } from '../../store/editEmployeeSlice'; +import editEmployee from '../../services/editEmployee'; +import Button from '../buttons/Button'; +import { closeModalEditEmployee } from '../../store/modalEditEmployeeSlice'; +import useToken from '../../hooks/useToken'; +import { EmployeeType } from '../../types'; +import findAllEmployees from '../../services/findAllEmployees'; type ModalEditEmployeeProps = { employee: EmployeeType @@ -26,7 +26,7 @@ function ModalEditEmployee({ employee }: ModalEditEmployeeProps) { const dispatch = useDispatch(); const pathName = usePathname(); const token = useToken(); - const { isModalOpen } = useSelector((state: RootState) => state.modal); + const { isModalOpenEditEmployee } = useSelector((state: RootState) => state.modalEditEmployee); const { loading, error } = useSelector((state: RootState) => state.editEmployee); const [formData, setFormData] = useState({ photo: '', fullName: '', position: '', admission: '', phone: '' }); const { pageSize, pageNumber } = useSelector((state: RootState) => state.pagination); @@ -51,20 +51,20 @@ function ModalEditEmployee({ employee }: ModalEditEmployeeProps) { useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') { - dispatch(closeModal()); + dispatch(closeModalEditEmployee()); setFormData({ photo: '', fullName: '', position: '', admission: '', phone: '' }); dispatch(clearError()); } }; - if (isModalOpen) { + if (isModalOpenEditEmployee) { window.addEventListener('keydown', handleKeyDown); } return () => { window.removeEventListener('keydown', handleKeyDown); }; - }, [isModalOpen, dispatch, pathName]); + }, [isModalOpenEditEmployee, dispatch, pathName]); const handleInputChange = (event: React.ChangeEvent) => { const { name, value } = event.target; @@ -91,9 +91,9 @@ function ModalEditEmployee({ employee }: ModalEditEmployeeProps) { Toast.fire({ icon: 'success', - title: 'Employee created successfully', + title: 'Employee edited successfully', }).then(() => { - dispatch(closeModal()); + dispatch(closeModalEditEmployee()); dispatch(resetEmployee()); if (token) { dispatch(findAllEmployees({ token, pageNumber, pageSize, column, direction, term })); } }); @@ -102,7 +102,7 @@ function ModalEditEmployee({ employee }: ModalEditEmployeeProps) { const handleCloseModal = () => { setFormData({ photo: '', fullName: '', position: '', admission: '', phone: '' }); - dispatch(closeModal()); + dispatch(closeModalEditEmployee()); dispatch(clearError()); }; diff --git a/frontend/src/components/modal/ModalEditUser.tsx b/frontend/src/components/modal/ModalEditUser.tsx new file mode 100644 index 0000000..b9725fe --- /dev/null +++ b/frontend/src/components/modal/ModalEditUser.tsx @@ -0,0 +1,205 @@ +/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +/* eslint-disable max-len */ + +import { useDispatch, useSelector } from 'react-redux'; +import { FaTimes } from 'react-icons/fa'; +import { useEffect, useState } from 'react'; +import Swal from 'sweetalert2'; +import Input from '../Input'; +import { AppDispatch, RootState } from '../../store'; +import { clearError, resetUser } from '../../store/editUserSlice'; +import Button from '../buttons/Button'; +import { closeModalEditUser } from '../../store/modalEditUserSlice'; +import editUser from '../../services/editUser'; +import useToken from '../../hooks/useToken'; +import findAllUsers from '../../services/findAllUsers'; +import { UserType } from '../../types'; + +type ModalEditUserProps = { + user: UserType +}; + +function ModalEditUser({ user }: ModalEditUserProps) { + const dispatch = useDispatch(); + const { isModalEditUserOpen } = useSelector((state: RootState) => state.modalEditUser); + const { loading, error } = useSelector((state: RootState) => state.editUser); + const { pageSize, pageNumber } = useSelector((state: RootState) => state.pagination); + const { column, direction } = useSelector((state: RootState) => state.sort); + const [formData, setFormData] = useState({ photo: '', fullName: '', username: '', email: '', role: '' }); + const { term } = useSelector((state: RootState) => state.searchTerm); + const token = useToken(); + + const { id, photo, fullName, username, email, role } = user; + + const idString = id?.toString() || ''; + + useEffect(() => { + if (user) { + setFormData({ + photo, + fullName, + username, + email, + role, + }); + } + }, [user, id, photo, fullName, username, email, role]); + + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + dispatch(closeModalEditUser()); + setFormData({ photo: '', fullName: '', username: '', email: '', role: '' }); + dispatch(clearError()); + } + }; + + if (isModalEditUserOpen) { + window.addEventListener('keydown', handleKeyDown); + } + + return () => { + window.removeEventListener('keydown', handleKeyDown); + }; + }, [isModalEditUserOpen, dispatch]); + + const handleInputChange = (event: React.ChangeEvent) => { + const { name, value } = event.target; + setFormData((prevData) => ({ ...prevData, [name]: value })); + }; + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + + const resultAction = await dispatch(editUser({ token, id: idString, userData: formData })); + + if (editUser.fulfilled.match(resultAction)) { + const Toast = Swal.mixin({ + toast: true, + position: 'top-end', + showConfirmButton: false, + timer: 3000, + timerProgressBar: true, + didOpen: (toast) => { + toast.onmouseenter = Swal.stopTimer; + toast.onmouseleave = Swal.resumeTimer; + }, + }); + + Toast.fire({ + icon: 'success', + title: 'User edited successfully', + }).then(() => { + dispatch(closeModalEditUser()); + dispatch(resetUser()); + if (token) { dispatch(findAllUsers({ token, pageNumber, pageSize, column, direction, term })); } + }); + } + }; + + const handleCloseModal = () => { + setFormData({ photo: '', fullName: '', username: '', email: '', role: '' }); + dispatch(closeModalEditUser()); + dispatch(clearError()); + }; + + const handleClickInside = (event: React.MouseEvent) => { + event.stopPropagation(); + }; + + return ( + +
+ + + + + +

+ Edit user +

+ + + + + + + + + + + +
+ ); +} + +export default ModalEditUser; diff --git a/frontend/src/components/table-employees/TableRow.tsx b/frontend/src/components/table-employees/TableRow.tsx index 55b54f4..f2dd70c 100644 --- a/frontend/src/components/table-employees/TableRow.tsx +++ b/frontend/src/components/table-employees/TableRow.tsx @@ -17,7 +17,7 @@ import ButtonDelete from '../buttons/ButtonDelete'; import ButtonEdit from '../buttons/ButtonEdit'; import { AppDispatch, RootState } from '../../store'; import { setColumn, setDirection } from '../../store/sortSlice'; -import { openModal } from '../../store/modalSlice'; +import { openModalEditEmployee } from '../../store/modalEditEmployeeSlice'; import { setSelectedEmployee } from '../../store/editEmployeeSlice'; type TableRowEmployeesProps = { @@ -51,9 +51,9 @@ function TableRowEmployees({ employee }: TableRowEmployeesProps) { setShowDetails(showDetails === 'hidden' ? '' : 'hidden'); }; - const handleEditClick = () => { + const handleEdit = () => { dispatch(setSelectedEmployee(employee)); - dispatch(openModal()); + dispatch(openModalEditEmployee()); }; return ( @@ -79,7 +79,7 @@ function TableRowEmployees({ employee }: TableRowEmployeesProps) { {isAdmin && ( - + @@ -127,7 +127,7 @@ function TableRowEmployees({ employee }: TableRowEmployeesProps) { {isAdmin && (
- +
)} diff --git a/frontend/src/components/table-user/TableRow.tsx b/frontend/src/components/table-user/TableRow.tsx index 94ff676..1e32ea6 100644 --- a/frontend/src/components/table-user/TableRow.tsx +++ b/frontend/src/components/table-user/TableRow.tsx @@ -16,6 +16,8 @@ import ButtonEdit from '../buttons/ButtonEdit'; import ButtonDelete from '../buttons/ButtonDelete'; import { AppDispatch, RootState } from '../../store'; import { setColumn, setDirection } from '../../store/sortSlice'; +import { setSelectedUser } from '../../store/editUserSlice'; +import { openModalEditUser } from '../../store/modalEditUserSlice'; type TableRowUsersProps = { user: UserType @@ -48,6 +50,11 @@ function TableRowUsers({ user }: TableRowUsersProps) { setShowDetails(showDetails === 'hidden' ? '' : 'hidden'); }; + const handleEdit = () => { + dispatch(setSelectedUser(user)); + dispatch(openModalEditUser()); + }; + return ( <> @@ -69,7 +76,7 @@ function TableRowUsers({ user }: TableRowUsersProps) { - + @@ -115,7 +122,7 @@ function TableRowUsers({ user }: TableRowUsersProps) {
- +
diff --git a/frontend/src/services/editUser.ts b/frontend/src/services/editUser.ts new file mode 100644 index 0000000..9f96623 --- /dev/null +++ b/frontend/src/services/editUser.ts @@ -0,0 +1,47 @@ +/* eslint-disable max-len */ + +import { createAsyncThunk } from '@reduxjs/toolkit'; + +type EditUserParams = { + token: string | null; + id: string; + userData: { + photo: string; + fullName: string; + username: string; + email: string; + role: string; + }; +}; + +const editUser = createAsyncThunk( + 'editUser', + async ({ userData, token, id }: EditUserParams, { rejectWithValue }) => { + try { + const response = await fetch(`http://localhost:8080/users/${id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify(userData), + }); + + if (!response.ok) { + const errorData = await response.json(); + console.error('Error fetching:', errorData.message); + return rejectWithValue(errorData.message); + } + + const user = await response.json(); + + return user; + } catch (error) { + console.error('Error fetching:', error); + return rejectWithValue('Something went wrong.'); + } + }, + +); + +export default editUser; diff --git a/frontend/src/store/editUserSlice.ts b/frontend/src/store/editUserSlice.ts new file mode 100644 index 0000000..efc4859 --- /dev/null +++ b/frontend/src/store/editUserSlice.ts @@ -0,0 +1,65 @@ +/* eslint-disable max-len */ +import { createSlice } from '@reduxjs/toolkit'; +import { UserType } from '../types'; +import register from '../services/register'; + +type EditUserState = { + user: UserType; + selectedUser: UserType | null; + loading: boolean; + error: string | null +}; + +const initialUser = { + id: null, + photo: '', + fullName: '', + username: '', + email: '', + password: '', + role: 'user', +}; + +const initialState: EditUserState = { + user: initialUser, + selectedUser: null, + loading: false, + error: null, +}; + +const editUserSlice = createSlice({ + name: 'register', + initialState, + reducers: { + clearError: (state) => { + state.error = null; + }, + resetUser: (state) => { + state.user = initialUser; + }, + setSelectedUser: (state, action) => { + state.selectedUser = action.payload; + }, + clearSelectedUSer: (state) => { + state.selectedUser = null; + }, + }, + extraReducers: (builder) => { + builder + .addCase(register.pending, (state) => { + state.loading = true; + state.error = null; + }) + .addCase(register.fulfilled, (state, action) => { + state.loading = false; + state.user = action.payload; + }) + .addCase(register.rejected, (state, action) => { + state.loading = false; + state.error = action.payload as string; + }); + }, +}); + +export const { clearError, resetUser, setSelectedUser, clearSelectedUSer } = editUserSlice.actions; +export default editUserSlice.reducer; diff --git a/frontend/src/store/findLoggedUserSlice.ts b/frontend/src/store/findLoggedUserSlice.ts index e8f9eb1..2056222 100644 --- a/frontend/src/store/findLoggedUserSlice.ts +++ b/frontend/src/store/findLoggedUserSlice.ts @@ -15,7 +15,7 @@ const initialState: FindLoggedUserState = { }; const findLoggedUserSlice = createSlice({ - name: 'allEmployees', + name: 'findLoggedUser', initialState, reducers: {}, extraReducers: (builder) => { diff --git a/frontend/src/store/index.ts b/frontend/src/store/index.ts index c7e32af..c4f7607 100644 --- a/frontend/src/store/index.ts +++ b/frontend/src/store/index.ts @@ -4,12 +4,17 @@ import registerReducer from './registerSlice'; import findAllUsersReducer from './findAllUsersSlice'; import findAllEmployeesReducer from './findAllEmployeesSlice'; import passwordChangeReducer from './passwordChangeSlice'; -import modalReducer from './modalSlice'; +import modalPasswordChangeReducer from './modalPasswordChangeSlice'; +import modalEditEmployeeReducer from './modalEditEmployeeSlice'; +import modalCreateEmployeeReducer from './modalCreateEmployeeSlice'; +import modalCreateUserReducer from './modalCreateUserSlice'; +import modalEditUserReducer from './modalEditUserSlice'; import paginationReducer from './paginationSlice'; import sortReducer from './sortSlice'; import searchTermReducer from './searchTermSlice'; import createEmployeeReducer from './createEmployeeSlice'; import editEmployeeReducer from './editEmployeeSlice'; +import editUserReducer from './editUserSlice'; import findLoggedUserReducer from './findLoggedUserSlice'; const store = configureStore({ @@ -20,12 +25,17 @@ const store = configureStore({ findAllEmployees: findAllEmployeesReducer, findLoggedUser: findLoggedUserReducer, passwordChange: passwordChangeReducer, - modal: modalReducer, + modalPasswordChange: modalPasswordChangeReducer, + modalEditEmployee: modalEditEmployeeReducer, + modalCreateEmployee: modalCreateEmployeeReducer, + modalCreateUser: modalCreateUserReducer, + modalEditUser: modalEditUserReducer, pagination: paginationReducer, sort: sortReducer, searchTerm: searchTermReducer, createEmployee: createEmployeeReducer, editEmployee: editEmployeeReducer, + editUser: editUserReducer, }, }); diff --git a/frontend/src/store/modalCreateEmployeeSlice.ts b/frontend/src/store/modalCreateEmployeeSlice.ts new file mode 100644 index 0000000..0226aed --- /dev/null +++ b/frontend/src/store/modalCreateEmployeeSlice.ts @@ -0,0 +1,27 @@ +/* eslint-disable max-len */ + +import { createSlice } from '@reduxjs/toolkit'; + +type ModalCreateEmployeeState = { + isModalCreateEmployeeOpen: boolean; +}; + +const initialState: ModalCreateEmployeeState = { + isModalCreateEmployeeOpen: false, +}; + +const modalCreateEmployeeSlice = createSlice({ + name: 'modalCreateEmployee', + initialState, + reducers: { + openModalCreateEmployee(state) { + state.isModalCreateEmployeeOpen = true; + }, + closeModalCreateEmployee(state) { + state.isModalCreateEmployeeOpen = false; + }, + }, +}); + +export const { openModalCreateEmployee, closeModalCreateEmployee } = modalCreateEmployeeSlice.actions; +export default modalCreateEmployeeSlice.reducer; diff --git a/frontend/src/store/modalCreateUserSlice.ts b/frontend/src/store/modalCreateUserSlice.ts new file mode 100644 index 0000000..a17aedb --- /dev/null +++ b/frontend/src/store/modalCreateUserSlice.ts @@ -0,0 +1,27 @@ +/* eslint-disable max-len */ + +import { createSlice } from '@reduxjs/toolkit'; + +type ModalCreateUserState = { + isModalCreateUserOpen: boolean; +}; + +const initialState: ModalCreateUserState = { + isModalCreateUserOpen: false, +}; + +const modalCreateUserSlice = createSlice({ + name: 'modalCreateUser', + initialState, + reducers: { + openModalCreateUser(state) { + state.isModalCreateUserOpen = true; + }, + closeModalCreateUser(state) { + state.isModalCreateUserOpen = false; + }, + }, +}); + +export const { openModalCreateUser, closeModalCreateUser } = modalCreateUserSlice.actions; +export default modalCreateUserSlice.reducer; diff --git a/frontend/src/store/modalEditEmployeeSlice.ts b/frontend/src/store/modalEditEmployeeSlice.ts new file mode 100644 index 0000000..b18378c --- /dev/null +++ b/frontend/src/store/modalEditEmployeeSlice.ts @@ -0,0 +1,27 @@ +/* eslint-disable max-len */ + +import { createSlice } from '@reduxjs/toolkit'; + +type ModalEditEmployeeState = { + isModalOpenEditEmployee: boolean; +}; + +const initialState: ModalEditEmployeeState = { + isModalOpenEditEmployee: false, +}; + +const modalEditEmployeeSlice = createSlice({ + name: 'modalEditEmployee', + initialState, + reducers: { + openModalEditEmployee(state) { + state.isModalOpenEditEmployee = true; + }, + closeModalEditEmployee(state) { + state.isModalOpenEditEmployee = false; + }, + }, +}); + +export const { openModalEditEmployee, closeModalEditEmployee } = modalEditEmployeeSlice.actions; +export default modalEditEmployeeSlice.reducer; diff --git a/frontend/src/store/modalEditUserSlice.ts b/frontend/src/store/modalEditUserSlice.ts new file mode 100644 index 0000000..e5ee239 --- /dev/null +++ b/frontend/src/store/modalEditUserSlice.ts @@ -0,0 +1,27 @@ +/* eslint-disable max-len */ + +import { createSlice } from '@reduxjs/toolkit'; + +type ModalEditUserState = { + isModalEditUserOpen: boolean; +}; + +const initialState: ModalEditUserState = { + isModalEditUserOpen: false, +}; + +const modalEditUserSlice = createSlice({ + name: 'modalEditUser', + initialState, + reducers: { + openModalEditUser(state) { + state.isModalEditUserOpen = true; + }, + closeModalEditUser(state) { + state.isModalEditUserOpen = false; + }, + }, +}); + +export const { openModalEditUser, closeModalEditUser } = modalEditUserSlice.actions; +export default modalEditUserSlice.reducer; diff --git a/frontend/src/store/modalPasswordChangeSlice.ts b/frontend/src/store/modalPasswordChangeSlice.ts new file mode 100644 index 0000000..1619035 --- /dev/null +++ b/frontend/src/store/modalPasswordChangeSlice.ts @@ -0,0 +1,26 @@ +/* eslint-disable max-len */ +import { createSlice } from '@reduxjs/toolkit'; + +type ModalPasswordChangeState = { + isModalPasswordChangeOpen: boolean; +}; + +const initialState: ModalPasswordChangeState = { + isModalPasswordChangeOpen: false, +}; + +const modalPasswordChangeSlice = createSlice({ + name: 'modalPasswordChange', + initialState, + reducers: { + openModalPasswordChange(state) { + state.isModalPasswordChangeOpen = true; + }, + closeModalPasswordChange(state) { + state.isModalPasswordChangeOpen = false; + }, + }, +}); + +export const { openModalPasswordChange, closeModalPasswordChange } = modalPasswordChangeSlice.actions; +export default modalPasswordChangeSlice.reducer; diff --git a/frontend/src/store/modalSlice.ts b/frontend/src/store/modalSlice.ts deleted file mode 100644 index cecdad8..0000000 --- a/frontend/src/store/modalSlice.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { createSlice } from '@reduxjs/toolkit'; - -type ModalPasswordChangeState = { - isModalOpen: boolean; -}; - -const initialState: ModalPasswordChangeState = { - isModalOpen: false, -}; - -const modalSlice = createSlice({ - name: 'modalPasswordChange', - initialState, - reducers: { - openModal(state) { - state.isModalOpen = true; - }, - closeModal(state) { - state.isModalOpen = false; - }, - }, -}); - -export const { openModal, closeModal } = modalSlice.actions; -export default modalSlice.reducer; From 688901a607f5012d5ae82d13a4f2cca90df7e6f9 Mon Sep 17 00:00:00 2001 From: mairess Date: Wed, 2 Oct 2024 15:05:25 -0300 Subject: [PATCH 4/7] fix: loading and error components, adjust column span --- frontend/src/components/Error.tsx | 8 +++++++- frontend/src/components/Loading.tsx | 5 +++++ frontend/src/components/table-employees/Table.tsx | 5 ++++- frontend/src/components/table-user/Table.tsx | 5 ++++- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/Error.tsx b/frontend/src/components/Error.tsx index 3fe4d20..70557f2 100644 --- a/frontend/src/components/Error.tsx +++ b/frontend/src/components/Error.tsx @@ -1,15 +1,21 @@ /* eslint-disable react/jsx-max-depth */ import Image from 'next/image'; +import { useSelector } from 'react-redux'; import iconErro from '../../public/iconError.svg'; import useWindowWidth from '../hooks/useWindowWidth'; import getColSpan from '../utils/handleColSpan'; +import { RootState } from '../store'; function Error() { const windowWidth = useWindowWidth(); + const { user } = useSelector((state: RootState) => state.findLoggedUser); + + const isAdmin = user?.role === 'ADMIN'; + return ( - +
error
diff --git a/frontend/src/components/Loading.tsx b/frontend/src/components/Loading.tsx index 3881f1c..2d1299e 100644 --- a/frontend/src/components/Loading.tsx +++ b/frontend/src/components/Loading.tsx @@ -1,9 +1,14 @@ /* eslint-disable max-len */ + function Loading() { return ( +
+
+
+ ); } diff --git a/frontend/src/components/table-employees/Table.tsx b/frontend/src/components/table-employees/Table.tsx index 452ed6e..99a2bb7 100644 --- a/frontend/src/components/table-employees/Table.tsx +++ b/frontend/src/components/table-employees/Table.tsx @@ -25,9 +25,12 @@ function TableEmployees() { const { column, direction } = useSelector((state: RootState) => state.sort); const { term } = useSelector((state: RootState) => state.searchTerm); const windowWidth = useWindowWidth(); + const { user } = useSelector((state: RootState) => state.findLoggedUser); const token = useToken(); const router = useRouter(); + const isAdmin = user?.role === 'ADMIN'; + useEffect(() => { if (token) { dispatch(findAllEmployees({ token, pageNumber, pageSize, column, direction, term })); } }, [token, dispatch, pageNumber, pageSize, column, direction]); @@ -45,7 +48,7 @@ function TableEmployees() { {loading && ( - +

Loading data...

diff --git a/frontend/src/components/table-user/Table.tsx b/frontend/src/components/table-user/Table.tsx index 9ab175e..b89f0b1 100644 --- a/frontend/src/components/table-user/Table.tsx +++ b/frontend/src/components/table-user/Table.tsx @@ -23,9 +23,12 @@ function TableUsers() { const { pageSize, pageNumber } = useSelector((state: RootState) => state.pagination); const { column, direction } = useSelector((state: RootState) => state.sort); const { term } = useSelector((state: RootState) => state.searchTerm); + const { user: userLogged } = useSelector((state: RootState) => state.findLoggedUser); const windowWidth = useWindowWidth(); const token = useToken(); const router = useRouter(); + + const isAdmin = userLogged?.role === 'ADMIN'; const isTokenExpired = error && error.includes('The Token has expired'); const isUserForbidden = error && error.includes('Access Denied'); @@ -55,7 +58,7 @@ function TableUsers() { - + From b5f4d191d7d4bc363f2c026cdc0421771b69495a Mon Sep 17 00:00:00 2001 From: mairess Date: Wed, 2 Oct 2024 15:09:42 -0300 Subject: [PATCH 5/7] fix: noDataFound component, adjust column span --- frontend/src/components/NoDataFound.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/NoDataFound.tsx b/frontend/src/components/NoDataFound.tsx index 7276ec0..97cddd5 100644 --- a/frontend/src/components/NoDataFound.tsx +++ b/frontend/src/components/NoDataFound.tsx @@ -1,8 +1,10 @@ /* eslint-disable react/jsx-max-depth */ import Image from 'next/image'; +import { useSelector } from 'react-redux'; import noDataFound from '../../public/noDataFound.svg'; import useWindowWidth from '../hooks/useWindowWidth'; import getColSpan from '../utils/handleColSpan'; +import { RootState } from '../store'; type NoDataFoundProps = { title: string @@ -10,13 +12,16 @@ type NoDataFoundProps = { function NoDataFound({ title }: NoDataFoundProps) { const windowWidth = useWindowWidth(); + const { user } = useSelector((state: RootState) => state.findLoggedUser); + + const isAdmin = user?.role === 'ADMIN'; return ( - +
From 92350f717a44a976aa72648a806d62f7618fd2b5 Mon Sep 17 00:00:00 2001 From: mairess Date: Wed, 2 Oct 2024 15:15:43 -0300 Subject: [PATCH 6/7] refactor: remove uncessary method toEntity from UserUpdateDto --- .../controller/dto/UserUpdateDto.java | 18 ------------ .../integration/UserIntegrationTest.java | 28 +++++++++++++++++++ 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/backend/src/main/java/org/maires/employee/controller/dto/UserUpdateDto.java b/backend/src/main/java/org/maires/employee/controller/dto/UserUpdateDto.java index 39eccd4..ad84c2d 100644 --- a/backend/src/main/java/org/maires/employee/controller/dto/UserUpdateDto.java +++ b/backend/src/main/java/org/maires/employee/controller/dto/UserUpdateDto.java @@ -5,7 +5,6 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; -import org.maires.employee.entity.User; import org.maires.employee.security.Role; import org.maires.employee.validation.EnumValidator; @@ -44,21 +43,4 @@ public record UserUpdateDto( String role ) { - - /** - * To entity user. - * - * @param existingUser the existing user - * @return the user - */ - public User toEntity(User existingUser) { - existingUser.setPhoto(photo); - existingUser.setFullName(fullName); - existingUser.setUsername(username); - existingUser.setEmail(email); - existingUser.setRole(Role.valueOf(role.toUpperCase())); - - return existingUser; - } - } \ No newline at end of file diff --git a/backend/src/test/java/org/maires/employee/integration/UserIntegrationTest.java b/backend/src/test/java/org/maires/employee/integration/UserIntegrationTest.java index 9edbfd0..6900a87 100644 --- a/backend/src/test/java/org/maires/employee/integration/UserIntegrationTest.java +++ b/backend/src/test/java/org/maires/employee/integration/UserIntegrationTest.java @@ -412,6 +412,34 @@ public void testUpdate() throws Exception { .andExpect(jsonPath("$.role").value("USER")); } + @Test + @DisplayName("Update user without password") + public void testUpdateUserWithoutPassword() throws Exception { + + User admin = new User("https://robohash.org/179.106.168.58.png", "Evangevaldo de Lima Soares", + "vange", "vange@example.com", "123456", + Role.ADMIN); + + User updatedUser = new User("https://robohash.org/179.106.168.66.png", "Gilmar de Castro", + "gilmar", "gilmar@example.com", "123456", + Role.USER); + + ObjectMapper objectMapper = new ObjectMapper(); + String updatedUserJson = objectMapper.writeValueAsString(updatedUser); + String userUrl = "/users/%s".formatted(userAdmin.getId()); + + mockMvc.perform(put(userUrl) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + tokenAdmin) + .contentType(MediaType.APPLICATION_JSON) + .content(updatedUserJson)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.photo").value("https://robohash.org/179.106.168.66.png")) + .andExpect(jsonPath("$.fullName").value("Gilmar de Castro")) + .andExpect(jsonPath("$.username").value("gilmar")) + .andExpect(jsonPath("$.email").value("gilmar@example.com")) + .andExpect(jsonPath("$.role").value("USER")); + } + @Test @DisplayName("Delete user") public void testDelete() throws Exception { From 5faf98953218b5f5152f99a88929ef726c77faf7 Mon Sep 17 00:00:00 2001 From: mairess Date: Wed, 2 Oct 2024 15:34:54 -0300 Subject: [PATCH 7/7] feat: add delete an user or employee --- .../src/components/buttons/ButtonDelete.tsx | 14 ++++++- .../components/table-employees/TableRow.tsx | 42 ++++++++++++++++++- .../src/components/table-user/TableRow.tsx | 42 ++++++++++++++++++- 3 files changed, 92 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/buttons/ButtonDelete.tsx b/frontend/src/components/buttons/ButtonDelete.tsx index 722db8b..bd7e95f 100644 --- a/frontend/src/components/buttons/ButtonDelete.tsx +++ b/frontend/src/components/buttons/ButtonDelete.tsx @@ -1,10 +1,20 @@ import Image from 'next/image'; import iconDelete from '../../../public/iconDelete.svg'; -function ButtonDelete() { +type ButtonDeleteProps = { + onClick?: () => void; +}; + +function ButtonDelete({ onClick = () => {} }: ButtonDeleteProps) { return ( - ); } diff --git a/frontend/src/components/table-employees/TableRow.tsx b/frontend/src/components/table-employees/TableRow.tsx index f2dd70c..10666f0 100644 --- a/frontend/src/components/table-employees/TableRow.tsx +++ b/frontend/src/components/table-employees/TableRow.tsx @@ -6,6 +6,7 @@ import React, { useState } from 'react'; import Image from 'next/image'; import { useDispatch, useSelector } from 'react-redux'; +import Swal from 'sweetalert2'; import { EmployeeType } from '../../types'; import iconChevronDown from '../../../public/iconChevronDown.svg'; import iconChevronUp from '../../../public/iconChevronUp.svg'; @@ -19,6 +20,8 @@ import { AppDispatch, RootState } from '../../store'; import { setColumn, setDirection } from '../../store/sortSlice'; import { openModalEditEmployee } from '../../store/modalEditEmployeeSlice'; import { setSelectedEmployee } from '../../store/editEmployeeSlice'; +import useToken from '../../hooks/useToken'; +import findAllEmployees from '../../services/findAllEmployees'; type TableRowEmployeesProps = { employee: EmployeeType @@ -27,9 +30,12 @@ type TableRowEmployeesProps = { function TableRowEmployees({ employee }: TableRowEmployeesProps) { const dispatch = useDispatch(); const [showDetails, setShowDetails] = useState('hidden'); + const { pageSize, pageNumber } = useSelector((state: RootState) => state.pagination); const { direction, column } = useSelector((state: RootState) => state.sort); const { user } = useSelector((state: RootState) => state.findLoggedUser); + const { term } = useSelector((state: RootState) => state.searchTerm); const windowWidth = useWindowWidth(); + const token = useToken(); const isAdmin = user?.role === 'ADMIN'; @@ -56,6 +62,38 @@ function TableRowEmployees({ employee }: TableRowEmployeesProps) { dispatch(openModalEditEmployee()); }; + const handleDelete = async () => { + const response = await fetch(`http://localhost:8080/employees/${employee.id}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + }); + + if (!response.ok) { + const errorData = await response.json(); + console.error('Error fetching:', errorData.message); + return null; + } + + const Toast = Swal.mixin({ + toast: true, + position: 'top-end', + showConfirmButton: false, + timer: 3000, + timerProgressBar: true, + didOpen: (toast) => { + toast.onmouseenter = Swal.stopTimer; + toast.onmouseleave = Swal.resumeTimer; + }, + }); + Toast.fire({ + icon: 'success', + title: 'Employee deleted successfully', + }).then(() => { if (token) { dispatch(findAllEmployees({ token, pageNumber, pageSize, column, direction, term })); } }); + }; + return ( <> @@ -80,7 +118,7 @@ function TableRowEmployees({ employee }: TableRowEmployeesProps) { - + )} @@ -128,7 +166,7 @@ function TableRowEmployees({ employee }: TableRowEmployeesProps) { {isAdmin && (
- +
)} diff --git a/frontend/src/components/table-user/TableRow.tsx b/frontend/src/components/table-user/TableRow.tsx index 1e32ea6..07b39cb 100644 --- a/frontend/src/components/table-user/TableRow.tsx +++ b/frontend/src/components/table-user/TableRow.tsx @@ -6,6 +6,7 @@ import React, { useState } from 'react'; import Image from 'next/image'; import { useDispatch, useSelector } from 'react-redux'; +import Swal from 'sweetalert2'; import { UserType } from '../../types'; import iconChevronDown from '../../../public/iconChevronDown.svg'; import iconChevronUp from '../../../public/iconChevronUp.svg'; @@ -18,6 +19,8 @@ import { AppDispatch, RootState } from '../../store'; import { setColumn, setDirection } from '../../store/sortSlice'; import { setSelectedUser } from '../../store/editUserSlice'; import { openModalEditUser } from '../../store/modalEditUserSlice'; +import findAllUsers from '../../services/findAllUsers'; +import useToken from '../../hooks/useToken'; type TableRowUsersProps = { user: UserType @@ -27,8 +30,11 @@ function TableRowUsers({ user }: TableRowUsersProps) { const dispatch = useDispatch(); const [showDetails, setShowDetails] = useState('hidden'); const { user: userLogged } = useSelector((state: RootState) => state.findLoggedUser); + const { pageSize, pageNumber } = useSelector((state: RootState) => state.pagination); const { direction, column } = useSelector((state: RootState) => state.sort); + const { term } = useSelector((state: RootState) => state.searchTerm); const windowWidth = useWindowWidth(); + const token = useToken(); const isAdmin = userLogged?.role === 'ADMIN'; @@ -55,6 +61,38 @@ function TableRowUsers({ user }: TableRowUsersProps) { dispatch(openModalEditUser()); }; + const handleDelete = async () => { + const response = await fetch(`http://localhost:8080/users/${user.id}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + }); + + if (!response.ok) { + const errorData = await response.json(); + console.error('Error fetching:', errorData.message); + return null; + } + + const Toast = Swal.mixin({ + toast: true, + position: 'top-end', + showConfirmButton: false, + timer: 3000, + timerProgressBar: true, + didOpen: (toast) => { + toast.onmouseenter = Swal.stopTimer; + toast.onmouseleave = Swal.resumeTimer; + }, + }); + Toast.fire({ + icon: 'success', + title: 'User deleted successfully', + }).then(() => { if (token) { dispatch(findAllUsers({ token, pageNumber, pageSize, column, direction, term })); } }); + }; + return ( <> @@ -77,7 +115,7 @@ function TableRowUsers({ user }: TableRowUsersProps) { - + @@ -123,7 +161,7 @@ function TableRowUsers({ user }: TableRowUsersProps) {
- +