Skip to content
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
10 changes: 8 additions & 2 deletions Convive/src/main/java/com/EC6/Convive/Config/MvcConfig.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
package com.EC6.Convive.Config;


import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.nio.file.Paths;
import org.springframework.beans.factory.annotation.Value;

@Configuration
public class MvcConfig implements WebMvcConfigurer {

@Value("${app.upload.dir}")
private String uploadDir;

@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("public/landing");
Expand All @@ -16,6 +23,5 @@ public void addViewControllers(ViewControllerRegistry registry) {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/uploads/**")
.addResourceLocations("file:uploads/");
}
.addResourceLocations("file:" + Paths.get(uploadDir).toAbsolutePath().toString() + "/"); }
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,9 @@ public class MoradorHomeController {

@GetMapping("/home")
public String dashboardMorador(@AuthenticationPrincipal CustomUserDetails userDetails, Model model) {
Usuario usuario = userDetails.getUsuario();
Usuario usuario = usuarioService.getByEmail(userDetails.getUsername());

List<Comunicado> comunicados = comunicadoService.findAllOrderByDate();

List<Reserva> reservas = reservaService.listByUser(usuario.getId());

model.addAttribute("usuario", usuario);
Expand All @@ -54,12 +53,14 @@ public String dashboardMorador(@AuthenticationPrincipal CustomUserDetails userDe

@GetMapping("/reservas")
public String listarReservasMorador(@AuthenticationPrincipal CustomUserDetails userDetails, Model model) {
Usuario usuario = userDetails.getUsuario();
Usuario usuario = usuarioService.getByEmail(userDetails.getUsername());

List<Reserva> reservas = reservaService.listByUser(usuario.getId()).stream()
.sorted(Comparator.comparing(Reserva::getInicio, Comparator.nullsLast(Comparator.naturalOrder())))
.toList();
List<AreaComum> areasComuns = areaComumService.listAll();
boolean temAreaAtiva = areasComuns.stream().anyMatch(a -> a.getStatusArea() == StatusArea.ATIVA);

model.addAttribute("usuario", usuario);
model.addAttribute("reservas", reservas);
model.addAttribute("areasComuns", areasComuns);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import java.util.UUID;
import java.util.UUID;import com.EC6.Convive.Security.CustomUserDetails;

@Controller
@RequiredArgsConstructor
Expand All @@ -28,11 +31,15 @@ public class MoradorUsuarioController {

@GetMapping
public String listarUsuarios(
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String search,
Model model) {

Usuario usuarioLogado = usuarioService.getByEmail(userDetails.getUsername());
model.addAttribute("usuario", usuarioLogado);

Pageable pageable = PageRequest.of(page, size);
Page<Usuario> moradorPage = usuarioService.findPaginatedAndFiltered(search, pageable);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
import com.EC6.Convive.Model.Ocorrencia;
import com.EC6.Convive.Model.Prioridade;
import com.EC6.Convive.Model.StatusOcorrencia;
import com.EC6.Convive.Model.Usuario;
import com.EC6.Convive.Security.CustomUserDetails;
import com.EC6.Convive.Service.OcorrenciaService;
import com.EC6.Convive.Service.UsuarioService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
Expand All @@ -19,9 +23,12 @@
public class TriagemOcorrenciasController {

private final OcorrenciaService ocorrenciaService;
private final UsuarioService usuarioService;

@GetMapping
public String gerenciarOcorrencias(Model model) {
public String gerenciarOcorrencias(@AuthenticationPrincipal CustomUserDetails userDetails, Model model) {
Usuario usuarioLogado = usuarioService.getByEmail(userDetails.getUsername());
model.addAttribute("usuario", usuarioLogado);
List<Ocorrencia> ocorrencias = ocorrenciaService.listAll();
model.addAttribute("ocorrencias", ocorrencias);
return "moderador/triagemOcorrencias";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
import com.EC6.Convive.Model.Reserva;
import com.EC6.Convive.Model.StatusReserva;
import com.EC6.Convive.Model.Usuario;
import com.EC6.Convive.Security.CustomUserDetails;
import com.EC6.Convive.Service.ReservaService;
import com.EC6.Convive.Service.UsuarioService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
Expand All @@ -26,7 +28,9 @@ public class TriagemReservasController {
private final ApplicationEventPublisher eventPublisher;

@GetMapping
public String gerenciarReservas(Model model) {
public String gerenciarReservas(@AuthenticationPrincipal CustomUserDetails userDetails, Model model) {
Usuario usuarioLogado = usuarioService.getByEmail(userDetails.getUsername());
model.addAttribute("usuario", usuarioLogado);
List<Reserva> reservas = reservaService.listAll();
model.addAttribute("reservas", reservas);
return "moderador/triagemReservas";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.EC6.Convive.Controller;


import com.EC6.Convive.Model.Usuario;
import com.EC6.Convive.Repository.UsuarioRepository;
import com.EC6.Convive.Service.FileStorageService;
import com.EC6.Convive.Service.UsuarioService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import java.security.Principal;

@Controller
@RequiredArgsConstructor
@RequestMapping("/perfil")
public class UserController {

private final FileStorageService fileStorageService;
private final UsuarioService usuarioService;


@PostMapping("/upload-foto")
public String uploadFotoPerfil(@RequestParam("foto") MultipartFile foto, Principal principal, RedirectAttributes redirectAttributes) {
try {
Usuario usuario = usuarioService.getByEmail(principal.getName());

String caminhoImagem = fileStorageService.salvarImagemPerfil(foto);

if (caminhoImagem != null) {
usuario.setFotoPerfil(caminhoImagem);
usuarioService.update(usuario.getId(), usuario);
}

redirectAttributes.addFlashAttribute("sucesso", "Foto de perfil atualizada com sucesso!");
} catch (Exception e) {
redirectAttributes.addFlashAttribute("erro", "Erro ao fazer upload da foto de perfil.");
}

return "redirect:/morador/home";
}
}
3 changes: 3 additions & 0 deletions Convive/src/main/java/com/EC6/Convive/Model/Usuario.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ public abstract class Usuario {
@Column(name = "isInadimplente")
private boolean isInadimplente;

@Column(name = "fotoPerfil")
private String fotoPerfil;

public abstract String getTipoUsuario();

}
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,23 @@ public String salvarImagemComunicado(MultipartFile arquivo) throws IOException {
Files.copy(arquivo.getInputStream(), caminhoDestino, StandardCopyOption.REPLACE_EXISTING);
return "/uploads/comunicados/" + nomeArquivoUnico;
}

public String salvarImagemPerfil(MultipartFile arquivo) throws IOException {
if (arquivo.isEmpty()) {
return null;
}

Path uploadPath = Paths.get(uploadDir).resolve("perfis");
if (!Files.exists(uploadPath)) {
Files.createDirectories(uploadPath);
}

String nomeArquivoOriginal = arquivo.getOriginalFilename();
String nomeArquivoUnico = UUID.randomUUID().toString() + "_perfil_" + nomeArquivoOriginal;

Path caminhoDestino = uploadPath.resolve(nomeArquivoUnico);
Files.copy(arquivo.getInputStream(), caminhoDestino, StandardCopyOption.REPLACE_EXISTING);

return "/uploads/perfis/" + nomeArquivoUnico;
}
}
10 changes: 10 additions & 0 deletions Convive/src/main/java/com/EC6/Convive/Service/UsuarioService.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ public void delete(UUID id) {
usuarioRepository.deleteById(id);
}

public Usuario update(UUID id, Usuario moradorAtualizado) {
Usuario usuario = searchById(id);

usuario.setNome(moradorAtualizado.getNome());
usuario.setEmail(moradorAtualizado.getEmail());
usuario.setFotoPerfil(moradorAtualizado.getFotoPerfil());

return usuarioRepository.save(usuario);
}

public Page<Usuario> findPaginatedAndFiltered(String search, Pageable pageable) {
if (search != null && !search.trim().isEmpty()) {
return usuarioRepository.findByNomeContainingIgnoreCaseOrEmailContainingIgnoreCase(search, search, pageable);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,19 @@

<nav class="hidden md:flex flex-col h-screen p-md gap-base bg-background text-primary w-64 docked left-0 h-full fixed border-r border-outline-variant/30">
<div class="mb-lg px-2 flex items-center gap-sm">
<div class="w-10 h-10 rounded-full bg-surface-container flex items-center justify-center text-primary font-bold">
C
</div>
<form th:action="@{/perfil/upload-foto}" method="post" enctype="multipart/form-data" id="formFotoPerfil" class="m-0">
<label class="w-10 h-10 rounded-full bg-surface-container flex items-center justify-center text-primary font-bold overflow-hidden cursor-pointer relative group transition-all">

<span th:if="${usuario?.fotoPerfil == null}" th:text="${usuario?.nome != null ? usuario.nome.substring(0,1).toUpperCase() : 'C'}">C</span>

<img th:if="${usuario?.fotoPerfil != null}" th:src="${usuario.fotoPerfil}" alt="Foto de Perfil" class="w-full h-full object-cover">

<div class="absolute inset-0 bg-black/40 hidden group-hover:flex items-center justify-center transition-opacity">
<span class="material-symbols-outlined text-white text-sm" style="font-size: 18px;">upload</span>
</div>
<input type="file" name="foto" accept="image/*" class="hidden" onchange="document.getElementById('formFotoPerfil').submit();">
</label>
</form>
<div>
<div class="text-h3 font-h3 font-bold text-primary" th:text="${usuario?.nome ?: 'Administrador'}">Convive</div>
<div class="font-body-sm text-body-sm text-on-surface-variant" th:text="${usuario != null ? usuario.getTipoUsuario() : 'Portal de Gestão'}">Portal do Morador</div>
Expand Down
47 changes: 23 additions & 24 deletions Convive/src/main/resources/templates/moderador/gestaoMoradores.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,32 +61,31 @@ <h1 class="font-h1 text-h1 text-on-surface">Gestão de Moradores</h1>
</thead>
<tbody class="divide-y divide-outline-variant/60 font-data-tabular text-data-tabular text-on-surface">

<tr th:each="morador : ${moradorPage.content}" class="hover:bg-surface-container-low/30 transition-colors group">
<td class="py-sm px-md flex items-center gap-sm">
<div class="w-8 h-8 rounded-full bg-surface-container-highest text-primary flex items-center justify-center font-bold text-[12px]"
th:text="${#strings.substring(morador.nome,0,1)}">M</div>
<span class="font-medium" th:text="${morador.nome}">Maria Silva</span>
<tr th:each="morador : ${moradorPage.content}" class="border-b border-outline-variant/20 hover:bg-surface-container-lowest/50 transition-colors">

<td class="px-6 py-3 text-primary font-medium">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-full bg-surface-container flex items-center justify-center text-primary font-bold overflow-hidden border border-outline-variant/30 flex-shrink-0">
<img th:if="${morador.fotoPerfil != null}"
th:src="${morador.fotoPerfil}"
alt="Foto de Perfil"
class="w-full h-full object-cover">

<span th:if="${morador.fotoPerfil == null}"
th:text="${morador.nome != null ? morador.nome.substring(0,1).toUpperCase() : 'U'}"
class="text-xs">U</span>
</div>

<span th:text="${morador.nome}">Nome do Usuário</span>
</div>
</td>

<td class="py-sm px-md" th:text="${morador.tipoUsuario == 'MORADOR' ? morador.apartamento : (morador.apartamento != null ? morador.apartamento : 'Admin/Sem Unidade')}">101</td>

<td class="py-sm px-md" th:text="${morador.email}">email@example.com</td>

<td class="py-sm px-md">
<span th:if="${!morador.inadimplente}" class="inline-flex items-center px-2 py-1 rounded-full bg-secondary-container text-on-secondary-container font-label-md text-[10px] uppercase leading-none">Adimplente</span>
<span th:if="${morador.inadimplente}" class="inline-flex items-center px-2 py-1 rounded-full bg-error-container text-on-error-container font-label-md text-[10px] uppercase leading-none">Inadimplente</span>
</td>

<td class="py-sm px-md text-right">
<button class="text-on-surface-variant hover:text-primary transition-colors p-xs rounded hover:bg-surface-container-low opacity-0 group-hover:opacity-100 focus:opacity-100"
th:data-id="${morador.id}"
th:data-nome="${morador.nome}"
th:data-email="${morador.email}"
th:data-apartamento="${morador.tipoUsuario == 'MORADOR' ? morador.apartamento : (morador.tipoUsuario == 'MODERADOR' ? morador.apartamento : '')}"
th:data-tipo="${morador.tipoUsuario == 'MORADOR' ? 'morador' : 'moderador'}"
onclick="abrirModal(this)">
<span class="material-symbols-outlined" data-icon="edit">edit</span>
</button>
<td class="px-6 py-3 text-on-surface-variant" th:text="${morador.email}">email@convive.com</td>
<td class="px-6 py-3 text-on-surface-variant" th:text="${morador.getTipoUsuario() == 'MODERADOR' ? (morador.apartamento != null ? morador.apartamento : 'N/A') : (morador.apartamento != null ? morador.apartamento : 'Não Atribuído')}">000</td>
<td class="px-6 py-3">
<span class="px-2 py-1 rounded text-xs font-semibold"
th:classappend="${morador.getTipoUsuario() == 'MODERADOR'} ? 'bg-primary/10 text-primary' : 'bg-secondary/10 text-secondary'"
th:text="${morador.getTipoUsuario()}">TIPO</span>
</td>
</tr>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.ui.Model;
import com.EC6.Convive.Service.UsuarioService;

import java.time.LocalDate;
import java.util.List;
Expand All @@ -29,9 +30,14 @@
public class MoradorHomeControllerTest {
@Mock
private ComunicadoService comunicadoService;
@Mock private ReservaService reservaService;
@Mock private AreaComumService areaComumService;
@Mock private Model model;
@Mock
private ReservaService reservaService;
@Mock
private AreaComumService areaComumService;
@Mock
private UsuarioService usuarioService; // Add this mock
@Mock
private Model model;
@InjectMocks
private MoradorHomeController moradorHomeController;

Expand All @@ -42,13 +48,17 @@ public class MoradorHomeControllerTest {
void setUp() {
usuarioMock = new Morador();
usuarioMock.setId(UUID.randomUUID());

usuarioMock.setEmail("teste@convive.com");

userDetails = new CustomUserDetails(usuarioMock);
}

@Test
void moradorHomeController_Dashboard_Sucesso() {
when(comunicadoService.findAllOrderByDate()).thenReturn(List.of(new Comunicado()));
when(reservaService.listByUser(usuarioMock.getId())).thenReturn(List.of(new Reserva()));
when(usuarioService.getByEmail(anyString())).thenReturn(usuarioMock); // Mock this call

String view = moradorHomeController.dashboardMorador(userDetails, model);

Expand All @@ -57,5 +67,4 @@ void moradorHomeController_Dashboard_Sucesso() {
verify(model).addAttribute(eq("comunicados"), anyList());
verify(model).addAttribute(eq("reservas"), anyList());
}

}
}
Loading