From b491aed19ebf44a052671692446791fe668e579c Mon Sep 17 00:00:00 2001 From: Raul Bob Date: Tue, 28 Apr 2026 11:13:19 +0000 Subject: [PATCH] #13904 - Added structured/detailed view for external message display --- .../ExternalMessageAdapterFacade.java | 28 ++++ .../de/symeda/sormas/api/i18n/Captions.java | 2 + .../src/main/resources/captions.properties | 2 + .../externalmessage/ExternalMessageForm.java | 130 +++++++++++++++--- .../themes/sormas/views/lab-message.scss | 9 ++ 5 files changed, 155 insertions(+), 16 deletions(-) diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/externalmessage/ExternalMessageAdapterFacade.java b/sormas-api/src/main/java/de/symeda/sormas/api/externalmessage/ExternalMessageAdapterFacade.java index 741000e2794..b6ab8d4d659 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/externalmessage/ExternalMessageAdapterFacade.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/externalmessage/ExternalMessageAdapterFacade.java @@ -1,3 +1,18 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package de.symeda.sormas.api.externalmessage; import java.util.Date; @@ -16,6 +31,7 @@ public interface ExternalMessageAdapterFacade { ExternalMessageResult> getExternalMessages(Date since); /** + * The method should return the HTML source to display a structured/simpler view of the message. * * @param message * message to be converted @@ -24,6 +40,18 @@ public interface ExternalMessageAdapterFacade { */ ExternalMessageResult convertToHTML(ExternalMessageDto message); + /** + * The method should return the HTML source to display a detailed view of the message. + * + * @param message + * message to be converted + * @return An ExternalMessageResult with a String that is a full/raw HTML representation of the message, + * Falls back to {@link #convertToHTML(ExternalMessageDto)} by default. + */ + default ExternalMessageResult convertToDetailedHTML(ExternalMessageDto message) { + return convertToHTML(message); + } + /** * * @param message diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java index 402f110b303..a143a8dc941 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java @@ -1979,6 +1979,7 @@ public interface Captions { String externalMessage_deleteNewlyCreatedCase = "externalMessage.deleteNewlyCreatedCase"; String externalMessage_deleteNewlyCreatedContact = "externalMessage.deleteNewlyCreatedContact"; String externalMessage_deleteNewlyCreatedEventParticipant = "externalMessage.deleteNewlyCreatedEventParticipant"; + String externalMessage_detailedView = "externalMessage.detailedView"; String ExternalMessage_disease = "ExternalMessage.disease"; String ExternalMessage_diseaseVariant = "ExternalMessage.diseaseVariant"; String ExternalMessage_diseaseVariantDetails = "ExternalMessage.diseaseVariantDetails"; @@ -2012,6 +2013,7 @@ public interface Captions { String ExternalMessage_sampleReceivedDate = "ExternalMessage.sampleReceivedDate"; String ExternalMessage_specimenCondition = "ExternalMessage.specimenCondition"; String ExternalMessage_status = "ExternalMessage.status"; + String externalMessage_structuredView = "externalMessage.structuredView"; String ExternalMessage_surveillanceReport = "ExternalMessage.surveillanceReport"; String ExternalMessage_type = "ExternalMessage.type"; String ExternalMessageCriteria_birthDateFrom = "ExternalMessageCriteria.birthDateFrom"; diff --git a/sormas-api/src/main/resources/captions.properties b/sormas-api/src/main/resources/captions.properties index 9ee5c7030f8..e00f16a8c9c 100644 --- a/sormas-api/src/main/resources/captions.properties +++ b/sormas-api/src/main/resources/captions.properties @@ -1716,6 +1716,8 @@ infrastructureImportAllowOverwrite=Overwrite existing entries with imported data #External Message ExternalMessage=Message ExternalMessage.labMessageDetails=Message details +externalMessage.structuredView=Structured view +externalMessage.detailedView=Detailed view ExternalMessage.labSampleId=Lab sample ID ExternalMessage.messageDateTime=Message date ExternalMessage.personBirthDateDD=Day of birth diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/externalmessage/ExternalMessageForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/externalmessage/ExternalMessageForm.java index 8e17999ab70..06d6e5e93f8 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/externalmessage/ExternalMessageForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/externalmessage/ExternalMessageForm.java @@ -1,3 +1,18 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2026 SORMAS Foundation gGmbH + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package de.symeda.sormas.ui.externalmessage; import static de.symeda.sormas.ui.utils.LayoutUtil.fluidRowLocs; @@ -10,26 +25,41 @@ import com.vaadin.ui.CustomLayout; import com.vaadin.ui.Label; import com.vaadin.ui.Panel; -import com.vaadin.v7.data.util.converter.Converter; +import com.vaadin.ui.TabSheet; +import com.vaadin.ui.VerticalLayout; import de.symeda.sormas.api.FacadeProvider; import de.symeda.sormas.api.externalmessage.ExternalMessageDto; import de.symeda.sormas.api.externalmessage.ExternalMessageResult; +import de.symeda.sormas.api.i18n.Captions; +import de.symeda.sormas.api.i18n.I18nProperties; import de.symeda.sormas.ui.utils.AbstractEditForm; import de.symeda.sormas.ui.utils.VaadinUiUtil; +@SuppressWarnings({ + "java:S110", // suppress sonar too many parents warning + "java:S2160" // equals not overridden; value-equality is not needed for Vaadin form components +}) public class ExternalMessageForm extends AbstractEditForm { private static final long serialVersionUID = -3859401780981133265L; //@formatter:off private static final String HTML_LAYOUT = - fluidRowLocs(ExternalMessageDto.UUID, ExternalMessageDto.MESSAGE_DATE_TIME) + + fluidRowLocs(ExternalMessageDto.UUID, ExternalMessageDto.MESSAGE_DATE_TIME) + fluidRowLocs(ExternalMessageDto.EXTERNAL_MESSAGE_DETAILS); //@formatter:on - private Panel detailsPanel; + private TabSheet detailsTabSheet; + private Panel structuredViewPanel; + @SuppressWarnings("java:S1450") // referenced in addFields, tab listener and setValue - cannot be local + private Panel detailedViewPanel; + private VerticalLayout structuredViewContainer; + private VerticalLayout detailedViewContainer; + private boolean structuredViewLoaded; + private boolean detailedViewLoaded; + @SuppressWarnings("java:S1948") // Logger is effectively serializable via its name private final Logger logger = LoggerFactory.getLogger(getClass()); public ExternalMessageForm() { @@ -40,10 +70,44 @@ public ExternalMessageForm() { protected void addFields() { addFields(ExternalMessageDto.UUID, ExternalMessageDto.MESSAGE_DATE_TIME); - detailsPanel = new Panel(); - detailsPanel.setHeightFull(); - detailsPanel.addStyleName("lab-message-details"); - getContent().addComponent(detailsPanel, ExternalMessageDto.EXTERNAL_MESSAGE_DETAILS); + structuredViewContainer = new VerticalLayout(); + structuredViewContainer.setMargin(false); + structuredViewContainer.setSizeUndefined(); + + structuredViewPanel = new Panel(); + structuredViewPanel.setSizeFull(); + structuredViewPanel.addStyleName("lab-message-structured-view"); + structuredViewPanel.setContent(structuredViewContainer); + + detailedViewContainer = new VerticalLayout(); + detailedViewContainer.setMargin(false); + detailedViewContainer.setSizeUndefined(); + + detailedViewPanel = new Panel(); + detailedViewPanel.setSizeFull(); + detailedViewPanel.addStyleName("lab-message-detailed-view"); + detailedViewPanel.setContent(detailedViewContainer); + + detailsTabSheet = new TabSheet(); + detailsTabSheet.setSizeFull(); + detailsTabSheet.addStyleName("lab-message-details"); + detailsTabSheet.addTab(structuredViewPanel, I18nProperties.getCaption(Captions.externalMessage_structuredView)); + detailsTabSheet.addTab(detailedViewPanel, I18nProperties.getCaption(Captions.externalMessage_detailedView)); + + detailsTabSheet.addSelectedTabChangeListener(event -> { + ExternalMessageDto msg = getValue(); + if (msg == null) { + return; + } + Component selected = event.getTabSheet().getSelectedTab(); + if (selected == structuredViewPanel) { + loadStructuredView(msg); + } else if (selected == detailedViewPanel) { + loadDetailedView(msg); + } + }); + + getContent().addComponent(detailsTabSheet, ExternalMessageDto.EXTERNAL_MESSAGE_DETAILS); } @Override @@ -52,23 +116,57 @@ protected String createHtmlLayout() { } @Override - public void setValue(ExternalMessageDto externalMessage) throws ReadOnlyException, Converter.ConversionException { + public void setValue(ExternalMessageDto externalMessage) { super.setValue(externalMessage); getFieldGroup().setReadOnly(true); + structuredViewLoaded = false; + detailedViewLoaded = false; + structuredViewContainer.removeAllComponents(); + detailedViewContainer.removeAllComponents(); + loadStructuredView(externalMessage); + detailsTabSheet.setSelectedTab(structuredViewPanel); + } + + private void loadStructuredView(ExternalMessageDto externalMessage) { + if (structuredViewLoaded) { + return; + } + try { + ExternalMessageResult result = FacadeProvider.getExternalLabResultsFacade().convertToHTML(externalMessage); + if (result.isSuccess()) { + CustomLayout layout = new CustomLayout(); + layout.setTemplateContents(result.getValue()); + structuredViewContainer.addComponent(layout); + } else { + structuredViewContainer.addComponent(createXmlDisplay(externalMessage.getExternalMessageDetails())); + VaadinUiUtil.showWarningPopup(result.getError()); + } + } catch (Exception e) { + structuredViewContainer.addComponent(createXmlDisplay(externalMessage.getExternalMessageDetails())); + logger.error(e.getMessage()); + } + structuredViewLoaded = true; + } + + private void loadDetailedView(ExternalMessageDto externalMessage) { + if (detailedViewLoaded) { + return; + } try { - ExternalMessageResult htmlConversionResult = FacadeProvider.getExternalLabResultsFacade().convertToHTML(externalMessage); - if (htmlConversionResult.isSuccess()) { - CustomLayout externalMessageDetails = new CustomLayout(); - externalMessageDetails.setTemplateContents(htmlConversionResult.getValue()); - detailsPanel.setContent(externalMessageDetails); + ExternalMessageResult result = FacadeProvider.getExternalLabResultsFacade().convertToDetailedHTML(externalMessage); + if (result.isSuccess()) { + CustomLayout layout = new CustomLayout(); + layout.setTemplateContents(result.getValue()); + detailedViewContainer.addComponent(layout); } else { - detailsPanel.setContent(createXmlDisplay(externalMessage.getExternalMessageDetails())); - VaadinUiUtil.showWarningPopup(htmlConversionResult.getError()); + detailedViewContainer.addComponent(createXmlDisplay(externalMessage.getExternalMessageDetails())); + VaadinUiUtil.showWarningPopup(result.getError()); } } catch (Exception e) { - detailsPanel.setContent(createXmlDisplay(externalMessage.getExternalMessageDetails())); + detailedViewContainer.addComponent(createXmlDisplay(externalMessage.getExternalMessageDetails())); logger.error(e.getMessage()); } + detailedViewLoaded = true; } private Component createXmlDisplay(String xml) { diff --git a/sormas-ui/src/main/webapp/VAADIN/themes/sormas/views/lab-message.scss b/sormas-ui/src/main/webapp/VAADIN/themes/sormas/views/lab-message.scss index 60f06866802..d8e6cc33c0c 100644 --- a/sormas-ui/src/main/webapp/VAADIN/themes/sormas/views/lab-message.scss +++ b/sormas-ui/src/main/webapp/VAADIN/themes/sormas/views/lab-message.scss @@ -5,6 +5,14 @@ user-select: text; } + .lab-message-structured-view .v-customlayout { + min-width: 500px; + } + + .lab-message-detailed-view .v-customlayout { + min-width: max-content; + } + .lab-message-processable, .lab-message-slider { .lab-message-details { @@ -20,6 +28,7 @@ } .no-required-border { + .v-textfield.v-required, .v-filterselect.v-required .v-filterselect-input, .v-customcomponent.v-required .v-datefield-textfield,