Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: edit employee #36

Merged
merged 7 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -134,8 +135,8 @@ public ResponseEntity<UserDto> 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
Expand All @@ -144,10 +145,10 @@ public ResponseEntity<UserDto> create(@Valid @RequestBody UserCreationDto userCr
@PreAuthorize("hasAnyAuthority('ADMIN')")
public ResponseEntity<UserDto> 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));

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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.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
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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", "[email protected]", "123456",
Role.ADMIN);

User updatedUser = new User("https://robohash.org/179.106.168.66.png", "Gilmar de Castro",
"gilmar", "[email protected]", "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("[email protected]"))
.andExpect(jsonPath("$.role").value("USER"));
}

@Test
@DisplayName("Delete user")
public void testDelete() throws Exception {
Expand Down
11 changes: 8 additions & 3 deletions frontend/src/app/dashboard-employees/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ 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/modal/ModalEditEmployee';

function DashboardEmployees() {
const { isModalOpen } = useSelector((state: RootState) => state.modalPasswordChange);
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();

if (isAuthenticated === null || !isAuthenticated) return null;
Expand All @@ -21,7 +24,9 @@ function DashboardEmployees() {

<div className="bg-white px-spacing-regular-20 mb-1">

{isModalOpen && <ModalCreateEmployee />}
{isModalCreateEmployeeOpen && <ModalCreateEmployee />}

{isModalOpenEditEmployee && selectedEmployee && <ModalEditEmployee employee={ selectedEmployee } />}

<SearchBar title="Employees" placeholder="Search Employee" />

Expand Down
11 changes: 8 additions & 3 deletions frontend/src/app/dashboard-users/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,24 @@ 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.modalPasswordChange);
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;

return (
<div className="bg-white px-spacing-regular-20 mb-1">

{isModalOpen && <ModalCreateUser />}
{isModalCreateUserOpen && <ModalCreateUser />}

{isModalEditUserOpen && selectedUser && <ModalEditUser user={ selectedUser } />}

<SearchBar title="Users" placeholder="Search User" />

Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/AuthFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import Link from 'next/link';
import { useDispatch } from 'react-redux';
import { openModal } from '../store/modalPasswordChangeSlice';
import { openModalPasswordChange } from '../store/modalPasswordChangeSlice';
import { AppDispatch } from '../store';

type AuthFooterProps = {
Expand All @@ -19,7 +19,7 @@ function AuthFooter({ forgotPassword = null, doNotHaveAccountText, doNotHaveAcco

const handleOpenModal = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
dispatch(openModal());
dispatch(openModalPasswordChange());
};

return (
Expand Down
8 changes: 7 additions & 1 deletion frontend/src/components/Error.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<tbody>
<tr>
<td colSpan={ getColSpan(windowWidth) } className="p-10">
<td colSpan={ getColSpan(windowWidth, isAdmin) } className="p-10">
<div className="flex justify-center">
<Image className="size-8" src={ iconErro } alt="error" />
</div>
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/FormLogin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ 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';

function FormLogin() {
const dispatch = useDispatch<AppDispatch>();
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);
Expand Down Expand Up @@ -80,7 +80,7 @@ function FormLogin() {

<>

{isModalOpen && <ModalChangePassword />}
{isModalPasswordChangeOpen && <ModalChangePassword />}

<form
className="flex flex-col gap-6 bg-light-neutral-100 border border-light-neutral-400 rounded-lg p-8 shadow-xl"
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/components/Loading.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
/* eslint-disable max-len */

function Loading() {
return (

<div className="flex justify-center items-center">

<div className="border-4 border-light-neutral-400 border-t-light-neutral-700 border-r-light-neutral-700 rounded-full size-6 animate-spin" />

</div>

);
}

Expand Down
7 changes: 6 additions & 1 deletion frontend/src/components/NoDataFound.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
/* 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
};

function NoDataFound({ title }: NoDataFoundProps) {
const windowWidth = useWindowWidth();
const { user } = useSelector((state: RootState) => state.findLoggedUser);

const isAdmin = user?.role === 'ADMIN';

return (

<tbody>

<tr>
<td colSpan={ getColSpan(windowWidth) } className="p-10 ">
<td colSpan={ getColSpan(windowWidth, isAdmin) } className="p-10 ">

<div className="flex justify-center">

Expand Down
8 changes: 6 additions & 2 deletions frontend/src/components/buttons/ButtonAdd.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
/* eslint-disable max-len */
import Image from 'next/image';
import { useDispatch } from 'react-redux';
import { usePathname } from 'next/navigation';
import add from '../../../public/add.svg';
import { openModal } from '../../store/modalPasswordChangeSlice';
import { openModalCreateEmployee } from '../../store/modalCreateEmployeeSlice';
import { openModalCreateUser } from '../../store/modalCreateUserSlice';
import { AppDispatch } from '../../store';

function ButtonAdd() {
const dispatch = useDispatch<AppDispatch>();
const pathName = usePathname();

const handleOpenModal = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
dispatch(openModal());
const openModal = pathName === '/dashboard-employees' ? openModalCreateEmployee() : openModalCreateUser();
dispatch(openModal);
};

return (
Expand Down
14 changes: 12 additions & 2 deletions frontend/src/components/buttons/ButtonDelete.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<button className="hover:bg-light-neutral-200 p-1 rounded">

<button
onClick={ onClick }
className="hover:bg-light-neutral-200 p-1 rounded"
>

<Image src={ iconDelete } alt="delete" width={ 24 } />

</button>
);
}
Expand Down
14 changes: 12 additions & 2 deletions frontend/src/components/buttons/ButtonEdit.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<button className="hover:bg-light-neutral-200 p-1 rounded">

<button
onClick={ onClick }
className="hover:bg-light-neutral-200 p-1 rounded"
>
<Image src={ iconEdit } alt="edit" width={ 24 } />

</button>

);
}

Expand Down
Loading
Loading