From 30306a4bb71abb605969e8e75fbc52c44ba26d45 Mon Sep 17 00:00:00 2001 From: Jarkko Pesonen <435495+jrkkp@users.noreply.github.com> Date: Mon, 11 Nov 2024 23:27:35 +0200 Subject: [PATCH] VKT(Frontend & Backend): Examiner contact request listing --- .../examiner/ExaminerContactRequestDTO.java | 13 +++ .../api/dto/examiner/ExaminerDetailsDTO.java | 3 +- .../examiner/ExaminerExamEventController.java | 3 +- .../EnrollmentAppointmentRepository.java | 6 ++ .../ExaminerExamEventRepository.java | 3 +- .../oph/vkt/service/ClerkExaminerService.java | 2 +- .../vkt/service/ExaminerDetailsService.java | 14 +++- .../java/fi/oph/vkt/util/ExaminerUtil.java | 19 ++++- .../src/styles/abstracts/common/_spacing.scss | 6 ++ .../listing/ExaminerContactRequestListing.tsx | 84 +++++++++++++++++++ .../listing/ExaminerExamEventListing.tsx | 2 +- .../vkt/src/interfaces/examinerDetails.ts | 7 ++ .../src/pages/examiner/ExaminerHomePage.tsx | 11 ++- 13 files changed, 158 insertions(+), 15 deletions(-) create mode 100644 backend/vkt/src/main/java/fi/oph/vkt/api/dto/examiner/ExaminerContactRequestDTO.java create mode 100644 frontend/packages/vkt/src/components/examinerExamEvent/listing/ExaminerContactRequestListing.tsx diff --git a/backend/vkt/src/main/java/fi/oph/vkt/api/dto/examiner/ExaminerContactRequestDTO.java b/backend/vkt/src/main/java/fi/oph/vkt/api/dto/examiner/ExaminerContactRequestDTO.java new file mode 100644 index 000000000..cedcd815f --- /dev/null +++ b/backend/vkt/src/main/java/fi/oph/vkt/api/dto/examiner/ExaminerContactRequestDTO.java @@ -0,0 +1,13 @@ +package fi.oph.vkt.api.dto.examiner; + +import jakarta.validation.constraints.NotEmpty; +import java.util.List; +import lombok.Builder; +import lombok.NonNull; + +@Builder +public record ExaminerContactRequestDTO( + @NonNull Long id, + @NonNull String lastName, + @NonNull String firstName +) {} diff --git a/backend/vkt/src/main/java/fi/oph/vkt/api/dto/examiner/ExaminerDetailsDTO.java b/backend/vkt/src/main/java/fi/oph/vkt/api/dto/examiner/ExaminerDetailsDTO.java index 1a6c510a2..e456cc43e 100644 --- a/backend/vkt/src/main/java/fi/oph/vkt/api/dto/examiner/ExaminerDetailsDTO.java +++ b/backend/vkt/src/main/java/fi/oph/vkt/api/dto/examiner/ExaminerDetailsDTO.java @@ -19,5 +19,6 @@ public record ExaminerDetailsDTO( @NonNull Boolean examLanguageSwedish, @NonNull Boolean isPublic, @NonNull @NotEmpty List municipalities, - @NonNull List examEvents + @NonNull List examEvents, + @NonNull List contactRequests ) {} diff --git a/backend/vkt/src/main/java/fi/oph/vkt/api/examiner/ExaminerExamEventController.java b/backend/vkt/src/main/java/fi/oph/vkt/api/examiner/ExaminerExamEventController.java index f11325060..ab9a9b388 100644 --- a/backend/vkt/src/main/java/fi/oph/vkt/api/examiner/ExaminerExamEventController.java +++ b/backend/vkt/src/main/java/fi/oph/vkt/api/examiner/ExaminerExamEventController.java @@ -4,9 +4,8 @@ import fi.oph.vkt.api.dto.examiner.ExaminerExamEventDTO; import fi.oph.vkt.service.ExaminerExamEventService; import io.swagger.v3.oas.annotations.Operation; -import java.util.List; - import jakarta.annotation.Resource; +import java.util.List; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; diff --git a/backend/vkt/src/main/java/fi/oph/vkt/repository/EnrollmentAppointmentRepository.java b/backend/vkt/src/main/java/fi/oph/vkt/repository/EnrollmentAppointmentRepository.java index 41e47c33b..ba377f77c 100644 --- a/backend/vkt/src/main/java/fi/oph/vkt/repository/EnrollmentAppointmentRepository.java +++ b/backend/vkt/src/main/java/fi/oph/vkt/repository/EnrollmentAppointmentRepository.java @@ -4,7 +4,9 @@ import fi.oph.vkt.model.Enrollment; import fi.oph.vkt.model.EnrollmentAppointment; import fi.oph.vkt.model.ExamEvent; +import fi.oph.vkt.model.Examiner; import fi.oph.vkt.model.Person; +import fi.oph.vkt.model.type.EnrollmentAppointmentStatus; import fi.oph.vkt.model.type.EnrollmentStatus; import java.util.List; import java.util.Optional; @@ -14,4 +16,8 @@ @Repository public interface EnrollmentAppointmentRepository extends BaseRepository { Optional findByIdAndAuthHash(final long id, final String paymentLinkHash); + List findByExaminerAndStatus( + final Examiner examiner, + final EnrollmentAppointmentStatus status + ); } diff --git a/backend/vkt/src/main/java/fi/oph/vkt/repository/ExaminerExamEventRepository.java b/backend/vkt/src/main/java/fi/oph/vkt/repository/ExaminerExamEventRepository.java index eb5d6f4c0..c27b32e17 100644 --- a/backend/vkt/src/main/java/fi/oph/vkt/repository/ExaminerExamEventRepository.java +++ b/backend/vkt/src/main/java/fi/oph/vkt/repository/ExaminerExamEventRepository.java @@ -4,5 +4,4 @@ import org.springframework.stereotype.Repository; @Repository -public interface ExaminerExamEventRepository extends BaseRepository { -} +public interface ExaminerExamEventRepository extends BaseRepository {} diff --git a/backend/vkt/src/main/java/fi/oph/vkt/service/ClerkExaminerService.java b/backend/vkt/src/main/java/fi/oph/vkt/service/ClerkExaminerService.java index b43488150..d94c44657 100644 --- a/backend/vkt/src/main/java/fi/oph/vkt/service/ClerkExaminerService.java +++ b/backend/vkt/src/main/java/fi/oph/vkt/service/ClerkExaminerService.java @@ -27,7 +27,7 @@ public List listExaminers() { return examinerRepository .getAllByDeletedAtIsNull() .stream() - .map(e -> ExaminerUtil.toExaminerDetailsDTO(e, baseUrlAPI)) + .map(e -> ExaminerUtil.toExaminerDetailsDTO(e, List.of(), baseUrlAPI)) .collect(Collectors.toList()); } } diff --git a/backend/vkt/src/main/java/fi/oph/vkt/service/ExaminerDetailsService.java b/backend/vkt/src/main/java/fi/oph/vkt/service/ExaminerDetailsService.java index a87b77d91..3dd5968d6 100644 --- a/backend/vkt/src/main/java/fi/oph/vkt/service/ExaminerDetailsService.java +++ b/backend/vkt/src/main/java/fi/oph/vkt/service/ExaminerDetailsService.java @@ -4,7 +4,10 @@ import fi.oph.vkt.api.dto.examiner.ExaminerDetailsInitDTO; import fi.oph.vkt.api.dto.examiner.ExaminerDetailsUpsertDTO; import fi.oph.vkt.audit.AuditService; +import fi.oph.vkt.model.EnrollmentAppointment; import fi.oph.vkt.model.Examiner; +import fi.oph.vkt.model.type.EnrollmentAppointmentStatus; +import fi.oph.vkt.repository.EnrollmentAppointmentRepository; import fi.oph.vkt.repository.ExaminerRepository; import fi.oph.vkt.service.onr.OnrService; import fi.oph.vkt.service.onr.PersonalData; @@ -25,6 +28,7 @@ public class ExaminerDetailsService { private final ExaminerRepository examinerRepository; + private final EnrollmentAppointmentRepository enrollmentAppointmentRepository; private final MunicipalityService municipalityService; private final OnrService onrService; private final AuditService auditService; @@ -86,19 +90,23 @@ public ExaminerDetailsDTO upsertExaminer(final String oid, ExaminerDetailsUpsert examinerRepository.saveAndFlush(examiner); final String baseUrlAPI = environment.getRequiredProperty("app.base-url.api"); - return ExaminerUtil.toExaminerDetailsDTO(examiner, baseUrlAPI); + return ExaminerUtil.toExaminerDetailsDTO(examiner, List.of(), baseUrlAPI); } @Transactional(readOnly = true) public ExaminerDetailsDTO getExaminer(final String oid) { // TODO Audit log entry - Examiner examiner = examinerRepository.getByOid(oid); + final Examiner examiner = examinerRepository.getByOid(oid); if (examiner == null) { throw new APIException(APIExceptionType.EXAMINER_NOT_FOUND); } final String baseUrlAPI = environment.getRequiredProperty("app.base-url.api"); + final List enrollmentAppointments = enrollmentAppointmentRepository.findByExaminerAndStatus( + examiner, + EnrollmentAppointmentStatus.CONTACT_CREATED + ); - return ExaminerUtil.toExaminerDetailsDTO(examiner, baseUrlAPI); + return ExaminerUtil.toExaminerDetailsDTO(examiner, enrollmentAppointments, baseUrlAPI); } @Transactional diff --git a/backend/vkt/src/main/java/fi/oph/vkt/util/ExaminerUtil.java b/backend/vkt/src/main/java/fi/oph/vkt/util/ExaminerUtil.java index 072c380a2..ebd1dc23f 100644 --- a/backend/vkt/src/main/java/fi/oph/vkt/util/ExaminerUtil.java +++ b/backend/vkt/src/main/java/fi/oph/vkt/util/ExaminerUtil.java @@ -1,11 +1,14 @@ package fi.oph.vkt.util; import fi.oph.vkt.api.dto.MunicipalityDTO; +import fi.oph.vkt.api.dto.examiner.ExaminerContactRequestDTO; import fi.oph.vkt.api.dto.examiner.ExaminerDetailsDTO; import fi.oph.vkt.api.dto.examiner.ExaminerExamEventDTO; +import fi.oph.vkt.model.EnrollmentAppointment; import fi.oph.vkt.model.Examiner; import fi.oph.vkt.model.ExaminerExamEvent; import fi.oph.vkt.model.Municipality; +import java.util.List; public class ExaminerUtil { @@ -13,6 +16,15 @@ public static MunicipalityDTO toMunicipalityDTO(final Municipality municipality) return MunicipalityDTO.builder().code(municipality.getCode()).build(); } + public static ExaminerContactRequestDTO toContactRequestDTO(final EnrollmentAppointment enrollmentAppointment) { + return ExaminerContactRequestDTO + .builder() + .id(enrollmentAppointment.getId()) + .firstName(enrollmentAppointment.getFirstName()) + .lastName(enrollmentAppointment.getLastName()) + .build(); + } + public static ExaminerExamEventDTO toExaminerExamEventDTO( final ExaminerExamEvent examinerExamEvent, final String baseUrlAPI @@ -38,7 +50,11 @@ public static ExaminerExamEventDTO toExaminerExamEventDTO( .build(); } - public static ExaminerDetailsDTO toExaminerDetailsDTO(final Examiner examiner, final String baseUrlAPI) { + public static ExaminerDetailsDTO toExaminerDetailsDTO( + final Examiner examiner, + final List enrollmentAppointments, + final String baseUrlAPI + ) { return ExaminerDetailsDTO .builder() .id(examiner.getId()) @@ -53,6 +69,7 @@ public static ExaminerDetailsDTO toExaminerDetailsDTO(final Examiner examiner, f .examLanguageFinnish(examiner.isExamLanguageFinnish()) .examLanguageSwedish(examiner.isExamLanguageSwedish()) .examEvents(examiner.getExamEvents().stream().map(e -> toExaminerExamEventDTO(e, baseUrlAPI)).toList()) + .contactRequests(enrollmentAppointments.stream().map(ExaminerUtil::toContactRequestDTO).toList()) .build(); } } diff --git a/frontend/packages/shared/src/styles/abstracts/common/_spacing.scss b/frontend/packages/shared/src/styles/abstracts/common/_spacing.scss index f87b9b753..ebeea6349 100644 --- a/frontend/packages/shared/src/styles/abstracts/common/_spacing.scss +++ b/frontend/packages/shared/src/styles/abstracts/common/_spacing.scss @@ -41,6 +41,12 @@ } } +.margin-bottom-xxl { + &#{&} { + margin-bottom: map.get($spacing, 'xxl'); + } +} + .margin-top-lg { &#{&} { margin-top: map.get($spacing, 'lg'); diff --git a/frontend/packages/vkt/src/components/examinerExamEvent/listing/ExaminerContactRequestListing.tsx b/frontend/packages/vkt/src/components/examinerExamEvent/listing/ExaminerContactRequestListing.tsx new file mode 100644 index 000000000..9913be824 --- /dev/null +++ b/frontend/packages/vkt/src/components/examinerExamEvent/listing/ExaminerContactRequestListing.tsx @@ -0,0 +1,84 @@ +import ChevronRightIcon from '@mui/icons-material/ChevronRight'; +import { TableCell, TableHead, TableRow } from '@mui/material'; +import { CustomButtonLink, CustomTable, Text } from 'shared/components'; +import { Color, Variant } from 'shared/enums'; + +import { useAppSelector } from 'configs/redux'; +import { AppRoutes } from 'enums/app'; +import { ContactRequest } from 'interfaces/examinerDetails'; +import { examinerDetailsSelector } from 'redux/selectors/examinerDetails'; + +const ExaminerExamEventListingHeader = () => { + return ( + + + Etunimi + Sukunimi + Toiminnot + + + ); +}; + +const ExaminerContactRequestListingRow = ({ + contactRequest, +}: { + contactRequest: ContactRequest; +}) => { + return ( + + + {contactRequest.firstName} + + + {contactRequest.lastName} + + + } + to={AppRoutes.ClerkEnrollmentContactRequestPage.replace( + /:enrollmentContactRequestId/, + contactRequest.id.toString(), + )} + > + Katso tiedot + + + + ); +}; + +const getRowDetails = (contactRequest: ContactRequest) => { + return ; +}; + +const ExaminerContactRequestsTable = ({ + contactRequests, +}: { + contactRequests: Array; +}) => { + return ( + } + /> + ); +}; + +export const ExaminerContactRequestListing = () => { + const { examiner } = useAppSelector(examinerDetailsSelector); + + return ( + examiner?.contactRequests && + examiner?.contactRequests?.length > 0 && ( + + ) + ); +}; diff --git a/frontend/packages/vkt/src/components/examinerExamEvent/listing/ExaminerExamEventListing.tsx b/frontend/packages/vkt/src/components/examinerExamEvent/listing/ExaminerExamEventListing.tsx index 81667fdd7..168077e69 100644 --- a/frontend/packages/vkt/src/components/examinerExamEvent/listing/ExaminerExamEventListing.tsx +++ b/frontend/packages/vkt/src/components/examinerExamEvent/listing/ExaminerExamEventListing.tsx @@ -150,7 +150,7 @@ export const ExaminerExamEventListing = () => { }; return ( -
+

{t('heading')}

diff --git a/frontend/packages/vkt/src/interfaces/examinerDetails.ts b/frontend/packages/vkt/src/interfaces/examinerDetails.ts index 939338caa..da9aab802 100644 --- a/frontend/packages/vkt/src/interfaces/examinerDetails.ts +++ b/frontend/packages/vkt/src/interfaces/examinerDetails.ts @@ -19,6 +19,12 @@ export interface ExaminerDetailsState { }; } +export interface ContactRequest extends WithId { + id: number; + firstName: string; + lastName: string; +} + export interface ExaminerDetails extends WithId { oid: string; lastName: string; @@ -30,6 +36,7 @@ export interface ExaminerDetails extends WithId { municipalities: Array; isPublic: boolean; examEvents: Array; + contactRequests: Array; } export interface ExaminerDetailsResponse diff --git a/frontend/packages/vkt/src/pages/examiner/ExaminerHomePage.tsx b/frontend/packages/vkt/src/pages/examiner/ExaminerHomePage.tsx index f483f2404..dcd2429d0 100644 --- a/frontend/packages/vkt/src/pages/examiner/ExaminerHomePage.tsx +++ b/frontend/packages/vkt/src/pages/examiner/ExaminerHomePage.tsx @@ -5,6 +5,7 @@ import { CustomButtonLink, H1, H2, Text } from 'shared/components'; import { APIResponseStatus, Color, Variant } from 'shared/enums'; import { DateUtils } from 'shared/utils'; +import { ExaminerContactRequestListing } from 'components/examinerExamEvent/listing/ExaminerContactRequestListing'; import { ExaminerExamEventListing } from 'components/examinerExamEvent/listing/ExaminerExamEventListing'; import { useCommonTranslation, @@ -32,7 +33,7 @@ const PublicInformation = () => { const { examEvents } = examiner; return ( -
+

{t('heading')}

{ keyPrefix: 'vkt.component.examinerOverview.contactRequests', }); // TODO Get contact requests from redux state & render them - const contactRequests = []; + const { examiner } = useAppSelector(examinerDetailsSelector); return ( -
+

{t('heading')}

- {contactRequests.length === 0 && ( + {examiner?.contactRequests?.length === 0 ? ( {t('labels.noContactRequests')} + ) : ( + )}
);