From 431ecaf6b6285ad29e75b25677a0696f0f050116 Mon Sep 17 00:00:00 2001 From: davij13 Date: Mon, 18 May 2026 15:32:13 -0300 Subject: [PATCH 1/8] feat: adiciona suporte para foto de perfil no cadastro de profissional --- .../HealthProfessionalControllerImpl.java | 7 +++++-- ...lthProfessionalApplicationServiceImpl.java | 19 +++++++++++++------ .../HealthProfessionalController.java | 8 +++++--- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/apps/api/src/main/java/br/org/apae/api/controllers/professional/HealthProfessionalControllerImpl.java b/apps/api/src/main/java/br/org/apae/api/controllers/professional/HealthProfessionalControllerImpl.java index e01613994..f5f8abef5 100644 --- a/apps/api/src/main/java/br/org/apae/api/controllers/professional/HealthProfessionalControllerImpl.java +++ b/apps/api/src/main/java/br/org/apae/api/controllers/professional/HealthProfessionalControllerImpl.java @@ -46,9 +46,12 @@ public HealthProfessionalControllerImpl( @Override public ResponseEntity createHealthProfessional( CreateHealthProfessionalDTO dto, - CreateProfessionalDocumentsDTO documentsDTO + CreateProfessionalDocumentsDTO documentsDTO, + MultipartFile profilePhoto ) { - HealthProfessionalResponseDTO createdProfessional = this.service.createProfessional(dto, documentsDTO); + HealthProfessionalResponseDTO createdProfessional = + this.service.createProfessional(dto, documentsDTO, profilePhoto); + return ResponseEntity.status(HttpStatus.CREATED).body(createdProfessional); } diff --git a/apps/api/src/main/java/br/org/apae/api/professional/application/internal/HealthProfessionalApplicationServiceImpl.java b/apps/api/src/main/java/br/org/apae/api/professional/application/internal/HealthProfessionalApplicationServiceImpl.java index 4424d7bf3..8260b5b3b 100644 --- a/apps/api/src/main/java/br/org/apae/api/professional/application/internal/HealthProfessionalApplicationServiceImpl.java +++ b/apps/api/src/main/java/br/org/apae/api/professional/application/internal/HealthProfessionalApplicationServiceImpl.java @@ -53,10 +53,13 @@ public HealthProfessionalApplicationServiceImpl(HealthProfessionalRepository rep this.serviceAreaApplicationService = serviceAreaApplicationService; } - @Override - @Transactional - public HealthProfessionalResponseDTO createProfessional(CreateHealthProfessionalDTO dto, - CreateProfessionalDocumentsDTO documentsDTO) { + @Override + @Transactional + public HealthProfessionalResponseDTO createProfessional( + CreateHealthProfessionalDTO dto, + CreateProfessionalDocumentsDTO documentsDTO, + MultipartFile profilePhoto + ) { if (dto.professionalDocument() != null && repository.existsByProfessionalDocument(dto.professionalDocument())) { throw new ProfessionalDocumentConflictException(); } @@ -73,9 +76,13 @@ public HealthProfessionalResponseDTO createProfessional(CreateHealthProfessional HealthProfessional professionalToSave = mapper.toEntity(dto, serviceAreaDto); HealthProfessional savedProfessional = repository.save(professionalToSave); documentsService.storeProfessionalDocuments(professionalToSave, documentsDTO); - return mapper.toResponseDTO(savedProfessional); - } + if (profilePhoto != null && !profilePhoto.isEmpty()) { + uploadProfessionalPhoto(savedProfessional.getId(), profilePhoto); + } + + return mapper.toResponseDTO(savedProfessional); + } @Override @Transactional public HealthProfessionalResponseDTO updateProfessional(UUID id, UpdateHealthProfessionalDTO dto) { diff --git a/apps/api/src/main/java/br/org/apae/api/professional/interfaces/controllers/HealthProfessionalController.java b/apps/api/src/main/java/br/org/apae/api/professional/interfaces/controllers/HealthProfessionalController.java index fa244b848..ba05835ec 100644 --- a/apps/api/src/main/java/br/org/apae/api/professional/interfaces/controllers/HealthProfessionalController.java +++ b/apps/api/src/main/java/br/org/apae/api/professional/interfaces/controllers/HealthProfessionalController.java @@ -44,9 +44,11 @@ public interface HealthProfessionalController { @ApiResponse(responseCode = "500", description = "Erro interno no servidor", content = @Content) }) @PostMapping(consumes = { MediaType.MULTIPART_FORM_DATA_VALUE }) - ResponseEntity createHealthProfessional(@RequestPart("professional") CreateHealthProfessionalDTO dto, - @ModelAttribute @Valid CreateProfessionalDocumentsDTO documentsDTO); - + ResponseEntity createHealthProfessional( + @RequestPart("professional") CreateHealthProfessionalDTO dto, + @ModelAttribute @Valid CreateProfessionalDocumentsDTO documentsDTO, + @RequestPart(value = "profilePhoto", required = false) MultipartFile profilePhoto + ); @GetMapping("/{id}/documents") @Operation( summary = "Listar documentos do profissional", From 53d769ca4529762db8b66fb80337ff181ea05a9d Mon Sep 17 00:00:00 2001 From: davij13 Date: Mon, 25 May 2026 16:17:11 -0300 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20adiciona=20persist=C3=AAncia=20de?= =?UTF-8?q?=20foto=20de=20perfil=20no=20MinIO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/app/professionals/edit/[id]/page.tsx | 2 +- ...lthProfessionalApplicationServiceImpl.java | 108 +++++++++++------- 2 files changed, 67 insertions(+), 43 deletions(-) diff --git a/apps/apae/src/app/professionals/edit/[id]/page.tsx b/apps/apae/src/app/professionals/edit/[id]/page.tsx index 0966dad33..58c279765 100644 --- a/apps/apae/src/app/professionals/edit/[id]/page.tsx +++ b/apps/apae/src/app/professionals/edit/[id]/page.tsx @@ -283,7 +283,7 @@ export default function AtualizarProfissional(): JSX.Element { const photoData = new FormData(); photoData.append("file", selectedPhoto); - const response = await fetch(`/api/professionals/${profissional.id}/photo`, { + const response = await fetch(`apae-geral/api/professionals/${profissional.id}/photo`, { method: "PATCH", body: photoData, }); diff --git a/apps/api/src/main/java/br/org/apae/api/professional/application/internal/HealthProfessionalApplicationServiceImpl.java b/apps/api/src/main/java/br/org/apae/api/professional/application/internal/HealthProfessionalApplicationServiceImpl.java index 8260b5b3b..43e16ee72 100644 --- a/apps/api/src/main/java/br/org/apae/api/professional/application/internal/HealthProfessionalApplicationServiceImpl.java +++ b/apps/api/src/main/java/br/org/apae/api/professional/application/internal/HealthProfessionalApplicationServiceImpl.java @@ -17,6 +17,10 @@ import br.org.apae.api.professional.domain.model.enums.Shift; import br.org.apae.api.professional.domain.repository.HealthProfessionalRepository; import br.org.apae.api.servicearea.application.interfaces.ServiceAreaApplicationService; +import br.org.apae.api.documents.application.interfaces.DocumentApplicationService; +import br.org.apae.api.documents.domain.enums.DocumentCategory; +import br.org.apae.api.documents.interfaces.dto.DocumentDTO; +import br.org.apae.api.documents.interfaces.dto.PutDocumentArgsDTO; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -24,7 +28,6 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; import java.time.DayOfWeek; import java.time.LocalDate; import java.time.LocalTime; @@ -32,9 +35,6 @@ import java.util.List; import java.util.UUID; import java.util.stream.Collectors; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; @Service public class HealthProfessionalApplicationServiceImpl implements HealthProfessionalApplicationService { @@ -43,23 +43,25 @@ public class HealthProfessionalApplicationServiceImpl implements HealthProfessio private final HealthProfessionalMapper mapper; private final ProfessionalDocumentsService documentsService; private final ServiceAreaApplicationService serviceAreaApplicationService; + private final DocumentApplicationService documentService; public HealthProfessionalApplicationServiceImpl(HealthProfessionalRepository repository, HealthProfessionalMapper mapper, ProfessionalDocumentsService documentsService, - ServiceAreaApplicationService serviceAreaApplicationService) { + ServiceAreaApplicationService serviceAreaApplicationService, DocumentApplicationService documentService) { this.repository = repository; this.mapper = mapper; this.documentsService = documentsService; this.serviceAreaApplicationService = serviceAreaApplicationService; + this.documentService = documentService; } - @Override - @Transactional - public HealthProfessionalResponseDTO createProfessional( - CreateHealthProfessionalDTO dto, - CreateProfessionalDocumentsDTO documentsDTO, - MultipartFile profilePhoto - ) { + @Override + @Transactional + public HealthProfessionalResponseDTO createProfessional( + CreateHealthProfessionalDTO dto, + CreateProfessionalDocumentsDTO documentsDTO, + MultipartFile profilePhoto + ) { if (dto.professionalDocument() != null && repository.existsByProfessionalDocument(dto.professionalDocument())) { throw new ProfessionalDocumentConflictException(); } @@ -75,6 +77,7 @@ public HealthProfessionalResponseDTO createProfessional( HealthProfessional professionalToSave = mapper.toEntity(dto, serviceAreaDto); HealthProfessional savedProfessional = repository.save(professionalToSave); + documentsService.storeProfessionalDocuments(professionalToSave, documentsDTO); if (profilePhoto != null && !profilePhoto.isEmpty()) { @@ -82,7 +85,8 @@ public HealthProfessionalResponseDTO createProfessional( } return mapper.toResponseDTO(savedProfessional); - } + } + @Override @Transactional public HealthProfessionalResponseDTO updateProfessional(UUID id, UpdateHealthProfessionalDTO dto) { @@ -104,8 +108,11 @@ public HealthProfessionalResponseDTO updateProfessional(UUID id, UpdateHealthPro : entityToUpdate.getServiceArea().getArea(); ServiceAreaResponseDTO serviceAreaDto = serviceAreaApplicationService.findServiceAreaByArea(area); + HealthProfessional updatedProfessional = mapper.updateEntityFromDto(entityToUpdate, dto, serviceAreaDto); + repository.save(updatedProfessional); + return mapper.toResponseDTO(updatedProfessional); } @@ -141,6 +148,7 @@ public Page findAllProfessionals(Boolean ativo, P Page page = ativo == null ? repository.findAll(pageable) : repository.findByAtivo(ativo, pageable); + return page.map(mapper::toResponseDTO); } @@ -149,6 +157,7 @@ public Page findAllProfessionals(Boolean ativo, P public void updateProfessionalDocuments(UUID id, UpdateProfessionalDocumentsDTO dto) { HealthProfessional professional = repository.findById(id) .orElseThrow(HealthProfessionalNotFoundException::new); + documentsService.updateProfessionalDocuments(professional, dto); } @@ -157,6 +166,7 @@ public void updateProfessionalDocuments(UUID id, UpdateProfessionalDocumentsDTO public void removeProfessionalDocument(UUID professionalId, UUID documentId) { HealthProfessional professional = repository.findById(professionalId) .orElseThrow(HealthProfessionalNotFoundException::new); + documentsService.removeProfessionalDocument(professional, documentId); } @@ -190,58 +200,63 @@ public void uploadProfessionalPhoto(UUID id, MultipartFile file) { throw new RuntimeException("Arquivo excede 5MB"); } - String fileName = UUID.randomUUID() - + "-" - + file.getOriginalFilename(); - - Path uploadPath = Paths.get("uploads"); - try { - Files.createDirectories(uploadPath); - - if (professional.getProfilePhoto() != null) { - - String oldFile = professional - .getProfilePhoto() - .replace("/uploads/", ""); - - Path oldFilePath = uploadPath.resolve(oldFile); - - Files.deleteIfExists(oldFilePath); - } - - Path filePath = uploadPath.resolve(fileName); - - Files.copy(file.getInputStream(), filePath); - - professional.setProfilePhoto("/uploads/" + fileName); + String fileName = UUID.randomUUID() + + "-" + + file.getOriginalFilename(); + + var document = documentService.putDocument( + new br.org.apae.api.documents.interfaces.dto.PutDocumentArgsDTO( + file.getInputStream(), + file.getContentType(), + "professional-profile-photo", + "profile", + professional.getId().toString(), + String.valueOf(LocalDate.now().getYear()) + ) + ); + + String photoUrl = documentService.getPresignedDocumentUrl( + new br.org.apae.api.documents.interfaces.dto.GetPresignedDocumentUrlArgsDTO( + document.owner(), + document.name(), + 60 * 60 * 24 * 7 + ) + ); + + professional.setProfilePhoto(photoUrl); repository.save(professional); - } catch (IOException e) { + } catch (Exception e) { throw new RuntimeException("Erro ao salvar foto"); } } - + private List generateSlots(LocalTime start, LocalTime end) { List slots = new ArrayList<>(); + LocalTime current = start; + while (current.isBefore(end)) { slots.add(current); current = current.plusMinutes(30); } + return slots; } @Override public List getAvailableTimes(UUID professionalId, LocalDate date) { + List occupied = repository.findOccupiedHours(professionalId, date); HealthProfessional professional = repository.findById(professionalId) .orElseThrow(HealthProfessionalNotFoundException::new); DayOfWeek dayOfWeek = date.getDayOfWeek(); + Day requestedDay = switch (dayOfWeek) { case MONDAY -> Day.SEGUNDA; case TUESDAY -> Day.TERCA; @@ -255,15 +270,24 @@ public List getAvailableTimes(UUID professionalId, LocalDate date) { boolean worksManha = professional.getAvailabilities().stream() .anyMatch(a -> a.getDay().equals(requestedDay) && a.getShift().equals(Shift.MANHA)); + boolean worksTarde = professional.getAvailabilities().stream() .anyMatch(a -> a.getDay().equals(requestedDay) && a.getShift().equals(Shift.TARDE)); List allSlots = new ArrayList<>(); - if (worksManha) allSlots.addAll(generateSlots(LocalTime.of(8, 0), LocalTime.of(12, 0))); - if (worksTarde) allSlots.addAll(generateSlots(LocalTime.of(13, 0), LocalTime.of(17, 0))); + + if (worksManha) { + allSlots.addAll(generateSlots(LocalTime.of(8, 0), LocalTime.of(12, 0))); + } + + if (worksTarde) { + allSlots.addAll(generateSlots(LocalTime.of(13, 0), LocalTime.of(17, 0))); + } if (date.isEqual(LocalDate.now())) { + LocalTime now = LocalTime.now(); + allSlots = allSlots.stream() .filter(slot -> slot.isAfter(now)) .collect(Collectors.toCollection(ArrayList::new)); @@ -278,4 +302,4 @@ public List getAvailableTimes(UUID professionalId, LocalDate date) { public boolean existsByProfessionalDocumentAndIdNot(String document, UUID professionalId) { return repository.existsByProfessionalDocumentAndIdNot(document, professionalId); } -} +} \ No newline at end of file From 687df67f1428c485f7c97cac14379331bfed056a Mon Sep 17 00:00:00 2001 From: davij13 Date: Wed, 3 Jun 2026 18:56:08 -0300 Subject: [PATCH 3/8] =?UTF-8?q?fix(professional):=20corrige=20upload=20de?= =?UTF-8?q?=20foto=20e=20integra=C3=A7=C3=A3o=20com=20documentos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../HealthProfessionalApplicationService.java | 8 +++- ...lthProfessionalApplicationServiceImpl.java | 40 ++++++++++--------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/apps/api/src/main/java/br/org/apae/api/professional/application/interfaces/HealthProfessionalApplicationService.java b/apps/api/src/main/java/br/org/apae/api/professional/application/interfaces/HealthProfessionalApplicationService.java index a85594bb8..540a1ecc7 100644 --- a/apps/api/src/main/java/br/org/apae/api/professional/application/interfaces/HealthProfessionalApplicationService.java +++ b/apps/api/src/main/java/br/org/apae/api/professional/application/interfaces/HealthProfessionalApplicationService.java @@ -16,7 +16,11 @@ public interface HealthProfessionalApplicationService { - HealthProfessionalResponseDTO createProfessional(CreateHealthProfessionalDTO dto, CreateProfessionalDocumentsDTO documentsDTO); + HealthProfessionalResponseDTO createProfessional( + CreateHealthProfessionalDTO dto, + CreateProfessionalDocumentsDTO documentsDTO, + MultipartFile profilePhoto + ); Page findAllProfessionals(Boolean ativo, Pageable pageable); @@ -25,7 +29,7 @@ public interface HealthProfessionalApplicationService { void activateProfessional(UUID id); void inactivateProfessional(UUID id); - + void reactivateProfessional(UUID id); HealthProfessionalResponseDTO findProfessionalById(UUID id); diff --git a/apps/api/src/main/java/br/org/apae/api/professional/application/internal/HealthProfessionalApplicationServiceImpl.java b/apps/api/src/main/java/br/org/apae/api/professional/application/internal/HealthProfessionalApplicationServiceImpl.java index 43e16ee72..c5efb38d8 100644 --- a/apps/api/src/main/java/br/org/apae/api/professional/application/internal/HealthProfessionalApplicationServiceImpl.java +++ b/apps/api/src/main/java/br/org/apae/api/professional/application/internal/HealthProfessionalApplicationServiceImpl.java @@ -21,6 +21,9 @@ import br.org.apae.api.documents.domain.enums.DocumentCategory; import br.org.apae.api.documents.interfaces.dto.DocumentDTO; import br.org.apae.api.documents.interfaces.dto.PutDocumentArgsDTO; +import br.org.apae.api.documents.interfaces.dto.PutDocumentArgsDTO; +import br.org.apae.api.documents.domain.enums.DocumentCategory; +import br.org.apae.api.documents.domain.enums.DocumentType; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -202,27 +205,26 @@ public void uploadProfessionalPhoto(UUID id, MultipartFile file) { try { - String fileName = UUID.randomUUID() - + "-" - + file.getOriginalFilename(); - - var document = documentService.putDocument( - new br.org.apae.api.documents.interfaces.dto.PutDocumentArgsDTO( - file.getInputStream(), - file.getContentType(), - "professional-profile-photo", - "profile", - professional.getId().toString(), - String.valueOf(LocalDate.now().getYear()) - ) + DocumentDTO document = documentService.putDocument( + PutDocumentArgsDTO.builder() + .stream(file.getInputStream()) + .category(DocumentCategory.PROFESSIONAL) + .type(DocumentType.PHOTO) + .contentType(file.getContentType()) + .owner(professional.getId().toString()) + .build() ); String photoUrl = documentService.getPresignedDocumentUrl( - new br.org.apae.api.documents.interfaces.dto.GetPresignedDocumentUrlArgsDTO( - document.owner(), - document.name(), - 60 * 60 * 24 * 7 - ) + br.org.apae.api.documents.interfaces.dto.GetPresignedDocumentUrlArgsDTO.builder() + .owner(document.owner()) + .name(document.name()) + .category(document.category()) + .type(document.type()) + .year(document.year()) + .id(document.id()) + .expiry(7, java.util.concurrent.TimeUnit.DAYS) + .build() ); professional.setProfilePhoto(photoUrl); @@ -230,7 +232,7 @@ public void uploadProfessionalPhoto(UUID id, MultipartFile file) { repository.save(professional); } catch (Exception e) { - throw new RuntimeException("Erro ao salvar foto"); + throw new RuntimeException("Erro ao salvar foto", e); } } From 43fffaca5c11fe30cf71d56a4ca040c3ebff3bc0 Mon Sep 17 00:00:00 2001 From: davij13 Date: Wed, 3 Jun 2026 20:14:12 -0300 Subject: [PATCH 4/8] fix[professional]: corrige erro ao atualizar profissional com foto --- .../apae/api/professional/domain/model/HealthProfessional.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api/src/main/java/br/org/apae/api/professional/domain/model/HealthProfessional.java b/apps/api/src/main/java/br/org/apae/api/professional/domain/model/HealthProfessional.java index d73765307..f87262d1c 100644 --- a/apps/api/src/main/java/br/org/apae/api/professional/domain/model/HealthProfessional.java +++ b/apps/api/src/main/java/br/org/apae/api/professional/domain/model/HealthProfessional.java @@ -52,7 +52,7 @@ public class HealthProfessional { @OneToMany(mappedBy = "professional", cascade = CascadeType.ALL, orphanRemoval = true) private List availabilities = new ArrayList<>(); - @Column(name = "foto_perfil") + @Column(name = "foto_perfil", columnDefinition = "TEXT") private String profilePhoto; protected HealthProfessional() { From f4ffc66c469bd21fb7ed9ec6bcf86bb2c67fa9d3 Mon Sep 17 00:00:00 2001 From: Wolgrand Date: Tue, 9 Jun 2026 00:52:18 -0300 Subject: [PATCH 5/8] =?UTF-8?q?fix:=20corrige=20bugs=20na=20integra=C3=A7?= =?UTF-8?q?=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/apae/src/app/professionals/create/page.tsx | 1 - apps/apae/src/app/professionals/edit/[id]/page.tsx | 7 ++++--- apps/apae/src/app/professionals/view/[id]/page.tsx | 4 +++- .../HealthProfessionalApplicationServiceImpl.java | 14 +------------- 4 files changed, 8 insertions(+), 18 deletions(-) diff --git a/apps/apae/src/app/professionals/create/page.tsx b/apps/apae/src/app/professionals/create/page.tsx index 6a6c0f369..83ddb3971 100644 --- a/apps/apae/src/app/professionals/create/page.tsx +++ b/apps/apae/src/app/professionals/create/page.tsx @@ -110,7 +110,6 @@ export default function CadastroProfissional(): JSX.Element { new Blob([JSON.stringify(payload)], { type: "application/json" }) ); - // profilePhoto faz referência ao componente do back, deve estar alinhado quando for fazer a integração if (values.photo) { formData.append("profilePhoto", values.photo); } diff --git a/apps/apae/src/app/professionals/edit/[id]/page.tsx b/apps/apae/src/app/professionals/edit/[id]/page.tsx index 58c279765..0081b5cea 100644 --- a/apps/apae/src/app/professionals/edit/[id]/page.tsx +++ b/apps/apae/src/app/professionals/edit/[id]/page.tsx @@ -194,7 +194,8 @@ export default function AtualizarProfissional(): JSX.Element { const curriculum = docs.find((d) => d.type === "CURRICULUM"); const volunteer = docs.find((d) => d.type === "VOLUNTEER_AGREEMENT"); const attachments = docs.filter((d) => d.type === "ATTACHMENTANY"); - return { curriculum, volunteer, attachments }; + const photo = docs.find((d) => d.type === "PHOTO"); + return { curriculum, volunteer, attachments, photo }; }, [docs]); function openRemoveModal(doc: DocumentWithUrl) { @@ -283,7 +284,7 @@ export default function AtualizarProfissional(): JSX.Element { const photoData = new FormData(); photoData.append("file", selectedPhoto); - const response = await fetch(`apae-geral/api/professionals/${profissional.id}/photo`, { + const response = await fetch(`/apae-geral/api/professionals/${profissional.id}/photo`, { method: "PATCH", body: photoData, }); @@ -605,7 +606,7 @@ export default function AtualizarProfissional(): JSX.Element { > diff --git a/apps/apae/src/app/professionals/view/[id]/page.tsx b/apps/apae/src/app/professionals/view/[id]/page.tsx index e38967fda..67f7f6686 100644 --- a/apps/apae/src/app/professionals/view/[id]/page.tsx +++ b/apps/apae/src/app/professionals/view/[id]/page.tsx @@ -24,6 +24,8 @@ export default function VisualizarProfissional() { const { documents, loading: loadingDocs, error: errorDocs } = useProfessionalDocuments(id); + const photoDoc = documents?.find((doc) => doc.type === "PHOTO"); + const disponibilidadeMatrix = useMemo(() => { const avs = profissional?.availabilities ?? []; @@ -92,7 +94,7 @@ export default function VisualizarProfissional() {
- + {dados.name?.charAt(0) || "P"} diff --git a/apps/api/src/main/java/br/org/apae/api/professional/application/internal/HealthProfessionalApplicationServiceImpl.java b/apps/api/src/main/java/br/org/apae/api/professional/application/internal/HealthProfessionalApplicationServiceImpl.java index c5efb38d8..85e03d2e1 100644 --- a/apps/api/src/main/java/br/org/apae/api/professional/application/internal/HealthProfessionalApplicationServiceImpl.java +++ b/apps/api/src/main/java/br/org/apae/api/professional/application/internal/HealthProfessionalApplicationServiceImpl.java @@ -215,19 +215,7 @@ public void uploadProfessionalPhoto(UUID id, MultipartFile file) { .build() ); - String photoUrl = documentService.getPresignedDocumentUrl( - br.org.apae.api.documents.interfaces.dto.GetPresignedDocumentUrlArgsDTO.builder() - .owner(document.owner()) - .name(document.name()) - .category(document.category()) - .type(document.type()) - .year(document.year()) - .id(document.id()) - .expiry(7, java.util.concurrent.TimeUnit.DAYS) - .build() - ); - - professional.setProfilePhoto(photoUrl); + professional.setProfilePhoto(document.id().toString()); repository.save(professional); From 9fe22627db08679e403a0bd8b8d62c38348227ac Mon Sep 17 00:00:00 2001 From: Wolgrand Date: Tue, 9 Jun 2026 11:22:22 -0300 Subject: [PATCH 6/8] refactor --- apps/apae/src/app/professionals/edit/[id]/page.tsx | 2 +- apps/apae/src/app/professionals/view/[id]/page.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/apae/src/app/professionals/edit/[id]/page.tsx b/apps/apae/src/app/professionals/edit/[id]/page.tsx index b496235e1..f594b2be9 100644 --- a/apps/apae/src/app/professionals/edit/[id]/page.tsx +++ b/apps/apae/src/app/professionals/edit/[id]/page.tsx @@ -288,7 +288,7 @@ export default function ProfessionalUpdate(): JSX.Element { const photoData = new FormData(); photoData.append("file", selectedPhoto); - const response = await fetch(`/apae-geral/api/professionals/${profissional.id}/photo`, { + const response = await fetch(`/apae-geral/api/professionals/${professional.id}/photo`, { method: "PATCH", body: photoData, }); diff --git a/apps/apae/src/app/professionals/view/[id]/page.tsx b/apps/apae/src/app/professionals/view/[id]/page.tsx index a1bd56edd..301cd77e7 100644 --- a/apps/apae/src/app/professionals/view/[id]/page.tsx +++ b/apps/apae/src/app/professionals/view/[id]/page.tsx @@ -26,8 +26,8 @@ export default function ViewProfessional() { const photoDoc = documents?.find((doc) => doc.type === "PHOTO"); - const disponibilidadeMatrix = useMemo(() => { - const avs = profissional?.availabilities ?? []; + const availabilityMatrix = useMemo(() => { + const avs = professional?.availabilities ?? []; return generateAvailabilityMatrix( avs.map((a) => ({ @@ -94,7 +94,7 @@ export default function ViewProfessional() {
- + {date.name?.charAt(0) || "P"} From c519972573e05148ba396a109d059ec1e4707d6b Mon Sep 17 00:00:00 2001 From: Wolgrand Date: Thu, 11 Jun 2026 00:36:01 -0300 Subject: [PATCH 7/8] fix --- .../src/app/professionals/edit/[id]/page.tsx | 155 ++++++++---------- .../src/app/professionals/view/[id]/page.tsx | 2 +- .../profissional/use-professional-photo.ts | 2 +- .../apae/src/services/profissional-service.ts | 7 +- 4 files changed, 73 insertions(+), 93 deletions(-) diff --git a/apps/apae/src/app/professionals/edit/[id]/page.tsx b/apps/apae/src/app/professionals/edit/[id]/page.tsx index aa9b6d5e4..e144abd06 100644 --- a/apps/apae/src/app/professionals/edit/[id]/page.tsx +++ b/apps/apae/src/app/professionals/edit/[id]/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { JSX } from "react"; +import { JSX, useMemo } from "react"; import { useRouter } from "next/navigation"; import { Controller, type SubmitHandler } from "react-hook-form"; import * as z from "zod"; @@ -10,6 +10,9 @@ import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { InputMask } from "@react-input/mask"; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { User } from "lucide-react"; + import { useGetByIdProfessional } from "@/hooks/profissional/use-get-by-id-profissional"; import { useUpdateProfessional } from "@/hooks/profissional/use-update-profissional"; @@ -38,8 +41,40 @@ export default function ProfessionalUpdate(): JSX.Element { const { updateProfessional, loading, error, success } = useUpdateProfessional(); const { upload, loadingDocs, errorDocs, successDocs } = useUpdateProfessionalDocuments(); - const photo = useProfessionalPhoto(); - const docs = useProfessionalDocs(professional?.id); + const { + fileInputRef, + selectedPhoto, + setSelectedPhoto, + photoPreviewUrl, + photoError, + photoSuccess, + clearPhoto, + uploadPhoto, + } = useProfessionalPhoto(); + + const { + docs: docsList, + docsLoading, + docsError, + curriculumFile, + setCurriculumFile, + volunteerFile, + setVolunteerFile, + attachmentFiles, + setAttachmentFiles, + hasAnyUpload, + removingIds, + removeModalOpen, + setRemoveModalOpen, + docToRemove, + isConfirmBusy, + openRemoveModal, + confirmRemove, + refreshDocuments, + buildFormData, + clearFiles, + isValidFile, + } = useProfessionalDocs(professional?.id); const form = useForm({ resolver: zodResolver(updateProfessionalSchema), @@ -61,45 +96,12 @@ export default function ProfessionalUpdate(): JSX.Element { }, [professional?.id, refreshDocuments]); const groupedDocs = useMemo(() => { - const curriculum = docs.find((d) => d.type === "CURRICULUM"); - const volunteer = docs.find((d) => d.type === "VOLUNTEER_AGREEMENT"); - const attachments = docs.filter((d) => d.type === "ATTACHMENTANY"); - const photo = docs.find((d) => d.type === "PHOTO"); - return { curriculum, volunteer, attachments, photo }; - }, [docs]); - - function openRemoveModal(doc: DocumentWithUrl) { - setDocToRemove(doc); - setRemoveModalOpen(true); - } - - async function confirmRemove() { - if (!professional?.id || !docToRemove) return; - - const idStr = String(docToRemove.id); - - setRemovingIds((prev) => { - const next = new Set(prev); - next.add(idStr); - return next; - }); - - try { - await removeProfessionalDocument(professional.id, docToRemove.id); - await refreshDocuments(professional.id); - setRemoveModalOpen(false); - setDocToRemove(null); - } catch (e: unknown) { - console.error(e); - alert((e as Error)?.message ?? "Erro ao remover documento"); - } finally { - setRemovingIds((prev) => { - const next = new Set(prev); - next.delete(idStr); - return next; - }); - } - } + const curriculum = docsList.find((d) => d.type === "CURRICULUM"); + const volunteer = docsList.find((d) => d.type === "VOLUNTEER_AGREEMENT"); + const attachments = docsList.filter((d) => d.type === "ATTACHMENTANY"); + const photoDoc = docsList.find((d) => d.type === "PHOTO"); + return { curriculum, volunteer, attachments, photo: photoDoc }; + }, [docsList]); const onSubmit: SubmitHandler = async (values) => { if (!professional?.id) return; @@ -111,35 +113,15 @@ export default function ProfessionalUpdate(): JSX.Element { const ok = await updateProfessional(professional.id, buildUpdatePayload(values, availabilities)); if (!ok) return; - if (docs.hasAnyUpload) { - await upload(professional.id, docs.buildFormData()); - docs.clearFiles(); - await docs.refreshDocuments(professional.id); + if (hasAnyUpload) { + await upload(professional.id, buildFormData()); + clearFiles(); + await refreshDocuments(professional.id); } if (selectedPhoto) { - setPhotoError(null); - setPhotoSuccess(false); - try { - const photoData = new FormData(); - photoData.append("file", selectedPhoto); - - const response = await fetch(`/apae-geral/api/professionals/${professional.id}/photo`, { - method: "PATCH", - body: photoData, - }); - - if (!response.ok) { - const body = await response.json().catch(() => ({})); - throw new Error(body?.message ?? "Erro ao enviar foto"); - } - - setPhotoSuccess(true); - setSelectedPhoto(null); - } catch (e: unknown) { - setPhotoError((e as Error)?.message ?? "Erro ao enviar foto"); - return; - } + const okPhoto = await uploadPhoto(professional.id); + if (!okPhoto) return; } router.push("/professionals"); @@ -338,26 +320,26 @@ export default function ProfessionalUpdate(): JSX.Element { /> @@ -365,6 +347,7 @@ export default function ProfessionalUpdate(): JSX.Element {

{loading ? "Salvando perfil..." : "Enviando documentos..."}

)} {error &&

{error}

} + {photoError &&

{photoError}

} {success &&

Profissional atualizado com sucesso!

}
diff --git a/apps/apae/src/app/professionals/view/[id]/page.tsx b/apps/apae/src/app/professionals/view/[id]/page.tsx index 87861797a..7501e3a4e 100644 --- a/apps/apae/src/app/professionals/view/[id]/page.tsx +++ b/apps/apae/src/app/professionals/view/[id]/page.tsx @@ -56,7 +56,7 @@ export default function ViewProfessional() {
- + {professional.name?.charAt(0) || "P"} diff --git a/apps/apae/src/hooks/profissional/use-professional-photo.ts b/apps/apae/src/hooks/profissional/use-professional-photo.ts index fd9342ba1..462e8e11f 100644 --- a/apps/apae/src/hooks/profissional/use-professional-photo.ts +++ b/apps/apae/src/hooks/profissional/use-professional-photo.ts @@ -26,7 +26,7 @@ export function useProfessionalPhoto() { try { const photoData = new FormData(); photoData.append("file", selectedPhoto); - const response = await fetch(`/api/professionals/${professionalId}/photo`, { + const response = await fetch(`/apae-geral/api/professionals/${professionalId}/photo`, { method: "PATCH", body: photoData, }); diff --git a/apps/apae/src/services/profissional-service.ts b/apps/apae/src/services/profissional-service.ts index 4f2c5bce0..e23dc0fbb 100644 --- a/apps/apae/src/services/profissional-service.ts +++ b/apps/apae/src/services/profissional-service.ts @@ -93,11 +93,8 @@ export async function removeProfessionalDocument( { method: "DELETE" }, ); - const data = await response.json(); - if (!response.ok) { + const data = await response.json().catch(() => ({})); throw new Error(data.message || "Erro ao remover documento"); - } - - return data; + } } \ No newline at end of file From ba4750d555aec39582917d7583251a0a88b242a8 Mon Sep 17 00:00:00 2001 From: FilipeKevyn Date: Tue, 16 Jun 2026 02:03:39 -0300 Subject: [PATCH 8/8] fix: removendo tipo de coluna desconhecido --- .../apae/api/professional/domain/model/HealthProfessional.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api/src/main/java/br/org/apae/api/professional/domain/model/HealthProfessional.java b/apps/api/src/main/java/br/org/apae/api/professional/domain/model/HealthProfessional.java index f87262d1c..d73765307 100644 --- a/apps/api/src/main/java/br/org/apae/api/professional/domain/model/HealthProfessional.java +++ b/apps/api/src/main/java/br/org/apae/api/professional/domain/model/HealthProfessional.java @@ -52,7 +52,7 @@ public class HealthProfessional { @OneToMany(mappedBy = "professional", cascade = CascadeType.ALL, orphanRemoval = true) private List availabilities = new ArrayList<>(); - @Column(name = "foto_perfil", columnDefinition = "TEXT") + @Column(name = "foto_perfil") private String profilePhoto; protected HealthProfessional() {