From aae4816b434ba6d0cc52fe92ff10f25f3df10ad9 Mon Sep 17 00:00:00 2001 From: Harold Asiimwe Date: Mon, 2 Mar 2026 07:01:54 +0300 Subject: [PATCH 01/30] #13853 - PoC clean up --- .../samples/DefaultDiseaseSectionLayout.java | 46 ++ .../ui/samples/DiseaseSectionLayout.java | 66 ++ .../ui/samples/PathogenTestController.java | 33 +- .../sormas/ui/samples/PathogenTestForm.java | 650 +++++++----------- .../TuberculosisDiseaseSectionLayout.java | 149 ++++ .../symeda/sormas/ui/utils/FieldHelper.java | 1 + 6 files changed, 518 insertions(+), 427 deletions(-) create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/DefaultDiseaseSectionLayout.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/DiseaseSectionLayout.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TuberculosisDiseaseSectionLayout.java diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/DefaultDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/DefaultDiseaseSectionLayout.java new file mode 100644 index 00000000000..172a0842305 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/DefaultDiseaseSectionLayout.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2018 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * + * 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.samples; + +import de.symeda.sormas.api.Disease; + +/** + * No-op section for diseases that have no extra fields beyond the common layout. + */ +public class DefaultDiseaseSectionLayout implements DiseaseSectionLayout { + + @Override + public String getHtmlLayout() { + return ""; + } + + @Override + public void bindFields(PathogenTestForm form) { + // no disease-specific fields + } + + @Override + public void unbindFields(PathogenTestForm form) { + // nothing to remove + } + + @Override + public Disease[] getDiseases() { + return new Disease[0]; + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/DiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/DiseaseSectionLayout.java new file mode 100644 index 00000000000..7df756e5ff6 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/DiseaseSectionLayout.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2018 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * + * 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.samples; + +import de.symeda.sormas.api.Disease; + +/** + * Encapsulates the disease-specific portion of PathogenTestForm. + * Each implementation adds/removes its own fields to/from the host form's field group + * and injects its layout template into the "diseaseSectionLoc" CustomLayout slot. + * + *

+ * Lifecycle: + *

    + *
  1. {@link #getHtmlLayout()} is called once to build the section's CustomLayout template.
  2. + *
  3. {@link #bindFields(PathogenTestForm)} is called to add fields to the form.
  4. + *
  5. {@link #unbindFields(PathogenTestForm)} is called before a new section replaces this one.
  6. + *
+ */ +public interface DiseaseSectionLayout { + + /** + * Returns the HTML template (using fluidRowLocs/loc helpers) for this section's fields. + * An empty string means no disease-specific fields. + */ + String getHtmlLayout(); + + /** + * Adds this section's fields to the host form's layout and field group. + * Called once after the section's CustomLayout has been installed. + */ + void bindFields(PathogenTestForm form); + + /** + * Removes this section's fields from the host form's layout and field group, + * clearing their values so they don't bleed into the saved DTO. + * Called before a new section replaces this one. + */ + void unbindFields(PathogenTestForm form); + + /** Returns the disease(s) this section handles, for logging/debugging. */ + Disease[] getDiseases(); + + /** Factory: returns the correct section implementation for the given disease. */ + static DiseaseSectionLayout forDisease(Disease disease) { + if (disease == Disease.TUBERCULOSIS || disease == Disease.LATENT_TUBERCULOSIS) { + return new TuberculosisDiseaseSectionLayout(); + } + return new DefaultDiseaseSectionLayout(); + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestController.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestController.java index a767a3ab3af..597749d1d25 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestController.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestController.java @@ -137,10 +137,10 @@ public CommitDiscardWrapperComponent getPathogenTestCreateComp ContactDto contact = FacadeProvider.getContactFacade().getByUuid(sampleDto.getAssociatedContact().getUuid()); associatedEventOrCaseOrContactDisease = contact.getDisease(); } - PathogenTestForm createForm = new PathogenTestForm(sampleDto, true, caseSampleCount, false, true, associatedEventOrCaseOrContactDisease); // Valid because jurisdiction doesn't matter for entities that are about to be created - // Defaulting the case disease as tested disease + PathogenTestForm createForm = new PathogenTestForm(sampleDto, true, caseSampleCount, false, true, associatedEventOrCaseOrContactDisease); pathogenTest.setTestedDisease(associatedEventOrCaseOrContactDisease); createForm.setValue(pathogenTest); + final CommitDiscardWrapperComponent editView = new CommitDiscardWrapperComponent<>(createForm, UiUtil.permitted(UserRight.PATHOGEN_TEST_CREATE), createForm.getFieldGroup()); @@ -354,14 +354,17 @@ public void savePathogenTests(List pathogenTests, SampleReferen /** * Handles the association of a pathogen test with a case. * Based on pathogen test results the following logic is applied: - * - *

Negative test result AND test result verified + * + *

+ * Negative test result AND test result verified *

    - *
  1. Tested disease == case disease AND test result != sample pathogen test result: Ask user whether to update the sample pathogen test result
  2. + *
  3. Tested disease == case disease AND test result != sample pathogen test result: Ask user whether to update the sample pathogen + * test result
  4. *
  5. Tested disease != case disease: Do nothing
  6. *
*

- *

Positive test result AND test result verified + *

+ * Positive test result AND test result verified *

    *
  1. Tested disease == case disease: Ask user whether to update the sample pathogen test result *
      @@ -372,12 +375,15 @@ public void savePathogenTests(List pathogenTests, SampleReferen *
    1. Tested disease != case disease: Ask user to create a new case for the tested disease
    2. *
    *

    - * - * @param pathogenTests the pathogen tests - * @param associatedCase the associated case - * @param suppressNavigateToCase whether to suppress navigation to the case - * - */ + * + * @param pathogenTests + * the pathogen tests + * @param associatedCase + * the associated case + * @param suppressNavigateToCase + * whether to suppress navigation to the case + * + */ private void handleAssociatedCase(List pathogenTests, CaseReferenceDto associatedCase, boolean suppressNavigateToCase) { if (!UiUtil.permitted(UserRight.CASE_EDIT)) { @@ -413,7 +419,6 @@ private void handleAssociatedCase(List pathogenTests, CaseRefer final boolean hasVerifiedTests = hasVerifiedPositiveTest || hasVerifiedNegativeTest; - // 1. Ask user to update sample overall result if latest test result is different // 2. Ask user to update disease variant if case variant is different // 3. Ask user if they want to confirm the case only if any of the tests are verified either positive or negative (not pending or other) @@ -436,7 +441,7 @@ private void handleAssociatedCase(List pathogenTests, CaseRefer // We decided this based on the intented text in the dialog but based on the test results instead of the sample overall result if (hasVerifiedPositiveTest) { // The final laboratory result of the sample the saved pathogen test belongs to is positive. <-- sample overall result - // However, the case cannot be automatically classified as a confirmed case because it is missing some information. + // However, the case cannot be automatically classified as a confirmed case because it is missing some information. // Do you want to set the case classification to confirmed anyway? this.showConfirmCaseDialog(c); // Case classification } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java index c0eb6e660fa..a75087dfa1f 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java @@ -36,16 +36,14 @@ import java.util.function.Consumer; import org.apache.commons.collections4.CollectionUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.vaadin.ui.CustomLayout; import com.vaadin.ui.Label; import com.vaadin.v7.data.util.converter.Converter; import com.vaadin.v7.ui.AbstractSelect.ItemCaptionMode; import com.vaadin.v7.ui.CheckBox; import com.vaadin.v7.ui.ComboBox; import com.vaadin.v7.ui.DateField; -import com.vaadin.v7.ui.Field; import com.vaadin.v7.ui.TextArea; import com.vaadin.v7.ui.TextField; @@ -71,7 +69,6 @@ import de.symeda.sormas.api.sample.SamplePurpose; import de.symeda.sormas.api.sample.SeroGroupSpecification; import de.symeda.sormas.api.sample.SerotypingMethod; -import de.symeda.sormas.api.utils.fieldaccess.UiFieldAccessCheckers; import de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers; import de.symeda.sormas.ui.therapy.DrugSusceptibilityForm; import de.symeda.sormas.ui.utils.AbstractEditForm; @@ -89,12 +86,12 @@ public class PathogenTestForm extends AbstractEditForm { private static final long serialVersionUID = -1218707278398543154L; - private final Logger logger = LoggerFactory.getLogger(getClass()); - private static final String PATHOGEN_TEST_HEADING_LOC = "pathogenTestHeadingLoc"; private static final String PRESCRIBER_HEADING_LOC = "prescriberHeading"; + private static final String DISEASE_SECTION_LOC = "diseaseSectionLoc"; + //@formatter:off private static final String HTML_LAYOUT = loc(PATHOGEN_TEST_HEADING_LOC) + @@ -109,12 +106,7 @@ public class PathogenTestForm extends AbstractEditForm { fluidRowLocs("", PathogenTestDto.LAB_DETAILS) + fluidRowLocs(6,PathogenTestDto.TEST_RESULT, 4, PathogenTestDto.TEST_RESULT_VERIFIED, 2,PathogenTestDto.PRELIMINARY) + fluidRowLocs(PathogenTestDto.TESTED_DISEASE_VARIANT, PathogenTestDto.TESTED_DISEASE_VARIANT_DETAILS) + - fluidRowLocs(PathogenTestDto.RIFAMPICIN_RESISTANT, PathogenTestDto.ISONIAZID_RESISTANT, "", "") + - fluidRowLocs(PathogenTestDto.TEST_SCALE, "") + - fluidRowLocs(PathogenTestDto.STRAIN_CALL_STATUS, "") + - fluidRowLocs(PathogenTestDto.SPECIE, "") + - fluidRowLocs(PathogenTestDto.PATTERN_PROFILE, "") + - fluidRowLocs(PathogenTestDto.DRUG_SUSCEPTIBILITY) + + loc(DISEASE_SECTION_LOC) + fluidRowLocs(4,PathogenTestDto.SEROTYPE, 4,PathogenTestDto.SEROTYPING_METHOD, 4,PathogenTestDto.SERO_TYPING_METHOD_TEXT) + fluidRowLocs(6,PathogenTestDto.SERO_GROUP_SPECIFICATION , 6, PathogenTestDto.SERO_GROUP_SPECIFICATION_TEXT) + fluidRowLocs(4,PathogenTestDto.GENOTYPE_RESULT,6, PathogenTestDto.GENOTYPE_RESULT_TEXT) + @@ -123,10 +115,6 @@ public class PathogenTestForm extends AbstractEditForm { fluidRowLocs(PathogenTestDto.CT_VALUE_E, PathogenTestDto.CT_VALUE_N) + fluidRowLocs(PathogenTestDto.CT_VALUE_RDRP, PathogenTestDto.CT_VALUE_S) + fluidRowLocs(PathogenTestDto.CT_VALUE_ORF_1, PathogenTestDto.CT_VALUE_RDRP_S) + - fluidRowLocs(PathogenTestDto.TUBE_NIL, PathogenTestDto.TUBE_NIL_GT10) + - fluidRowLocs(PathogenTestDto.TUBE_AG_TB1, PathogenTestDto.TUBE_AG_TB1_GT10) + - fluidRowLocs(PathogenTestDto.TUBE_AG_TB2, PathogenTestDto.TUBE_AG_TB2_GT10) + - fluidRowLocs(PathogenTestDto.TUBE_MITOGENE, PathogenTestDto.TUBE_MITOGENE_GT10) + fluidRowLocs(PathogenTestDto.TEST_RESULT_TEXT) + fluidRowLocs(PRESCRIBER_HEADING_LOC) + fluidRowLocs(PathogenTestDto.PRESCRIBER_PHYSICIAN_CODE, "") + @@ -203,14 +191,6 @@ public class PathogenTestForm extends AbstractEditForm { } }); - public static final Map> PATTERN_PROFILE_VISIBILITY_CONDITIONS = Collections.unmodifiableMap(new HashMap<>() { - - { - put(PathogenTestDto.TESTED_DISEASE, Collections.unmodifiableList(Arrays.asList(Disease.LATENT_TUBERCULOSIS, Disease.TUBERCULOSIS))); - put(PathogenTestDto.TEST_TYPE, Collections.unmodifiableList(Arrays.asList(PathogenTestType.MIRU_PATTERN_CODE))); - } - }); - public static final Map> PCR_TEST_SPECIFICATION_VISIBILITY_CONDITIONS = Collections.unmodifiableMap(new HashMap<>() { { @@ -235,13 +215,15 @@ public class PathogenTestForm extends AbstractEditForm { private ComboBox pcrTestSpecification; private Disease disease; private TextField typingIdField; - private ComboBox specieField; private ComboBox genoTypingCB; private TextField genoTypingResultTextTF; private ComboBox seroGrpSepcCB; private TextField seroGrpSpecTxt; + // Disease section swap support + private DiseaseSectionLayout activeSection = new DefaultDiseaseSectionLayout(); + private CustomLayout diseaseSectionPanel; public PathogenTestForm( AbstractSampleForm sampleForm, @@ -399,7 +381,10 @@ public void setValue(PathogenTestDto newFieldValue) throws ReadOnlyException, Co testResultField.setValue(newFieldValue.getTestResult()); } typingIdField.setValue(newFieldValue.getTypingId()); - specieField.setValue(newFieldValue.getSpecie()); + ComboBox specieFieldDynamic = getField(PathogenTestDto.SPECIE); + if (specieFieldDynamic != null) { + specieFieldDynamic.setValue(newFieldValue.getSpecie()); + } if (!genoTypingCB.isReadOnly()) { genoTypingCB.setValue(newFieldValue.getGenoTypeResult()); @@ -417,7 +402,9 @@ public void setValue(PathogenTestDto newFieldValue) throws ReadOnlyException, Co seroGrpSpecTxt.setValue(newFieldValue.getSeroGroupSpecificationText()); } - drugSusceptibilityField.forceUpdateDrugSusceptibilityFields(); + if (drugSusceptibilityField != null) { + drugSusceptibilityField.forceUpdateDrugSusceptibilityFields(); + } markAsDirty(); } @@ -428,6 +415,12 @@ protected void addFields() { pathogenTestHeadingLabel.addStyleName(H3); getContent().addComponent(pathogenTestHeadingLabel, PATHOGEN_TEST_HEADING_LOC); + // Install the disease section panel — a nested CustomLayout whose template is swapped on disease change + diseaseSectionPanel = new CustomLayout(); + diseaseSectionPanel.setTemplateContents(activeSection.getHtmlLayout()); + diseaseSectionPanel.setWidth(100, Unit.PERCENTAGE); + getContent().addComponent(diseaseSectionPanel, DISEASE_SECTION_LOC); + addDateField(PathogenTestDto.REPORT_DATE, DateField.class, 0); CheckBox viaLimsField = addField(PathogenTestDto.VIA_LIMS); addField(PathogenTestDto.EXTERNAL_ID); @@ -538,58 +531,10 @@ protected void addFields() { } TextField seroTypeTF = addField(PathogenTestDto.SEROTYPE, TextField.class); - NullableOptionGroup rifampicinResistantField = addField(PathogenTestDto.RIFAMPICIN_RESISTANT, NullableOptionGroup.class); - rifampicinResistantField.setVisible(false); - - NullableOptionGroup isoniazidResistantField = addField(PathogenTestDto.ISONIAZID_RESISTANT, NullableOptionGroup.class); - isoniazidResistantField.setVisible(false); - - ComboBox testScaleField = addField(PathogenTestDto.TEST_SCALE, ComboBox.class); - testScaleField.setVisible(false); - - ComboBox strainCallStatusField = addField(PathogenTestDto.STRAIN_CALL_STATUS, ComboBox.class); - strainCallStatusField.setItemCaptionMode(ItemCaptionMode.ID_TOSTRING); - strainCallStatusField.setVisible(false); - - specieField = addField(PathogenTestDto.SPECIE, ComboBox.class); - specieField.setVisible(false); - - TextField patternProfileField = addField(PathogenTestDto.PATTERN_PROFILE, TextField.class); - patternProfileField.setVisible(false); - - drugSusceptibilityField = (DrugSusceptibilityForm) addField( - PathogenTestDto.DRUG_SUSCEPTIBILITY, - new DrugSusceptibilityForm( - FieldVisibilityCheckers.getNoop(), - UiFieldAccessCheckers.getDefault(true, FacadeProvider.getConfigFacade().getCountryLocale()))); - drugSusceptibilityField.setCaption(null); - //drugSusceptibilityField.setVisible(false); - addToVisibleAllowedFields(drugSusceptibilityField); - - if (FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { - //tuberculosis-pcr test specification - FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.RIFAMPICIN_RESISTANT, RIFAMPICIN_RESISTANT_VISIBILITY_CONDITIONS, true); - - //tuberculosis-microscopy test specification - FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.TEST_SCALE, TEST_SCALE_VISIBILITY_CONDITIONS, true); - - //tuberculosis-beijinggenotyping test specification - FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.STRAIN_CALL_STATUS, STRAIN_CALL_STATUS_VISIBILITY_CONDITIONS, true); - - //tuberculosis-spoligotyping test specification - FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.SPECIE, SPECIE_VISIBILITY_CONDITIONS, true); - - //tuberculosis-miru-code test specification - Map> tuberculosisMiruCodeDependencies = new HashMap<>() { - - { - put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.TUBERCULOSIS, Disease.LATENT_TUBERCULOSIS)); - put(PathogenTestDto.TEST_TYPE, Arrays.asList(PathogenTestType.MIRU_PATTERN_CODE)); - } - }; - FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.PATTERN_PROFILE, tuberculosisMiruCodeDependencies, true); - //FieldHelper.setRequiredWhen(getFieldGroup(), PathogenTestDto.PATTERN_PROFILE, tuberculosisMiruCodeDependencies); - } + // Bind the initial disease section (default = no-op; swapped via swapDiseaseSection() on disease change) + activeSection = DiseaseSectionLayout.forDisease(disease); + diseaseSectionPanel.setTemplateContents(activeSection.getHtmlLayout()); + activeSection.bindFields(this); seroTypeTF.setVisible(false); @@ -598,7 +543,7 @@ protected void addFields() { seroGrpSepcCB = addField(PathogenTestDto.SERO_GROUP_SPECIFICATION, ComboBox.class); seroGrpSepcCB.setVisible(false); seroGrpSpecTxt = addField(PathogenTestDto.SERO_GROUP_SPECIFICATION_TEXT, TextField.class); - + TextField cqValueField = addField(FieldConfiguration.withConversionError(PathogenTestDto.CQ_VALUE, Validations.onlyNumbersAllowed)); if (!FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { cqValueField.setVisible(false); @@ -622,334 +567,6 @@ protected void addFields() { PathogenTestDto.CT_VALUE_ORF_1, PathogenTestDto.CT_VALUE_RDRP_S); - //@formatter:off - addFields( - FieldConfiguration.builder(PathogenTestDto.TUBE_NIL) - .validationMessageProperty(Validations.onlyNumbersAllowed) - .valueChangeListener(e -> { - final String tubeNilFieldValue = (String) e.getProperty().getValue(); - final NullableOptionGroup tubeNilGt10Field = getField(PathogenTestDto.TUBE_NIL_GT10); - final Float tubeNilValue = getValue().getTubeNil(); - final Boolean tubeNilGt10Value = getValue().getTubeNilGT10(); - - // we are called for a new entry - if(tubeNilValue == null - && tubeNilGt10Value == null - && tubeNilFieldValue == null - && tubeNilGt10Field.getNullableValue() == null) { - tubeNilGt10Field.select(false); - return; - } - - if(tubeNilFieldValue == null) { - tubeNilGt10Field.select(false); - return; - } - Float tubeNilNewValue = null; - try { - tubeNilNewValue = Float.parseFloat(tubeNilFieldValue); - } catch (NumberFormatException ex) { - // if it is not a number we clear the field - getField(PathogenTestDto.TUBE_NIL).clear(); - tubeNilGt10Field.select(false); - return; - } - // now we have a current and old value - if(tubeNilNewValue > 10) { - tubeNilGt10Field.select(true); - } else { - tubeNilGt10Field.select(false); - } - }) - .build(), - FieldConfiguration.builder(PathogenTestDto.TUBE_AG_TB1) - .validationMessageProperty(Validations.onlyNumbersAllowed) - .valueChangeListener(e -> { - final String tubeAgTb1FieldValue = (String) e.getProperty().getValue(); - final NullableOptionGroup tubeAgTb1Gt10Field = getField(PathogenTestDto.TUBE_AG_TB1_GT10); - final Float tubeAgTb1Value = getValue().getTubeAgTb1(); - final Boolean tubeAgTb1Gt10Value = getValue().getTubeAgTb1GT10(); - - // we are called for a new entry - if(tubeAgTb1Value == null - && tubeAgTb1Gt10Value == null - && tubeAgTb1FieldValue == null - && tubeAgTb1Gt10Field.getNullableValue() == null) { - tubeAgTb1Gt10Field.select(false); - return; - } - - if(tubeAgTb1FieldValue == null) { - tubeAgTb1Gt10Field.select(false); - return; - } - Float tubeAgTb1NewValue = null; - try { - tubeAgTb1NewValue = Float.parseFloat(tubeAgTb1FieldValue); - } catch (NumberFormatException ex) { - // if it is not a number we clear the field - getField(PathogenTestDto.TUBE_AG_TB1).clear(); - tubeAgTb1Gt10Field.select(false); - return; - } - // now we have a current and old value - if(tubeAgTb1NewValue > 10) { - tubeAgTb1Gt10Field.select(true); - } else { - tubeAgTb1Gt10Field.select(false); - } - }) - .build(), - FieldConfiguration.builder(PathogenTestDto.TUBE_AG_TB2) - .validationMessageProperty(Validations.onlyNumbersAllowed) - .valueChangeListener(e -> { - final String tubeAgTb2FieldValue = (String) e.getProperty().getValue(); - final NullableOptionGroup tubeAgTb2Gt10Field = getField(PathogenTestDto.TUBE_AG_TB2_GT10); - final Float tubeAgTb2Value = getValue().getTubeAgTb2(); - final Boolean tubeAgTb2Gt10Value = getValue().getTubeAgTb2GT10(); - - // we are called for a new entry - if(tubeAgTb2Value == null - && tubeAgTb2Gt10Value == null - && tubeAgTb2FieldValue == null - && tubeAgTb2Gt10Field.getNullableValue() == null) { - tubeAgTb2Gt10Field.select(false); - return; - } - - if(tubeAgTb2FieldValue == null) { - tubeAgTb2Gt10Field.select(false); - return; - } - Float tubeAgTb2NewValue = null; - try { - tubeAgTb2NewValue = Float.parseFloat(tubeAgTb2FieldValue); - } catch (NumberFormatException ex) { - // if it is not a number we clear the field - getField(PathogenTestDto.TUBE_AG_TB2).clear(); - tubeAgTb2Gt10Field.select(false); - return; - } - // now we have a current and old value - if(tubeAgTb2NewValue > 10) { - tubeAgTb2Gt10Field.select(true); - } else { - tubeAgTb2Gt10Field.select(false); - } - }) - .build(), - FieldConfiguration.builder(PathogenTestDto.TUBE_MITOGENE) - .validationMessageProperty(Validations.onlyNumbersAllowed) - .valueChangeListener(e -> { - final String tubeMitogeneFieldValue = (String) e.getProperty().getValue(); - final NullableOptionGroup tubeMitogeneGt10Field = getField(PathogenTestDto.TUBE_MITOGENE_GT10); - final Float tubeMitogeneValue = getValue().getTubeMitogene(); - final Boolean tubeMitogeneGt10Value = getValue().getTubeMitogeneGT10(); - - // we are called for a new entry - if(tubeMitogeneValue == null - && tubeMitogeneGt10Value == null - && tubeMitogeneFieldValue == null - && tubeMitogeneGt10Field.getNullableValue() == null) { - tubeMitogeneGt10Field.select(false); - return; - } - - if(tubeMitogeneFieldValue == null) { - tubeMitogeneGt10Field.select(false); - return; - } - Float tubeMitogeneNewValue = null; - try { - tubeMitogeneNewValue = Float.parseFloat(tubeMitogeneFieldValue); - } catch (NumberFormatException ex) { - // if it is not a number we clear the field - getField(PathogenTestDto.TUBE_MITOGENE).clear(); - tubeMitogeneGt10Field.select(false); - return; - } - // now we have a current and old value - if(tubeMitogeneNewValue > 10) { - tubeMitogeneGt10Field.select(true); - } else { - tubeMitogeneGt10Field.select(false); - } - }) - .build()); - //@formatter:on - - //@formatter:off - addFields( - FieldConfiguration.builder(PathogenTestDto.TUBE_NIL_GT10).valueChangeListener(event -> { - final Object propertySingleValue = event.getProperty().getValue() instanceof Collection - ? ((Collection) event.getProperty().getValue()).stream().findFirst().orElse(null) - : event.getProperty().getValue(); - final Float tubeNilValue = getValue().getTubeNil(); - - // we are called for a new entry or initial calls - if(propertySingleValue == null && tubeNilValue == null) { - final NullableOptionGroup tubeNilGt10Field = getField(PathogenTestDto.TUBE_NIL_GT10); - tubeNilGt10Field.select(false); - return; - } - final boolean checked = Boolean.TRUE.equals(propertySingleValue); - final Field tubeNilField = getField(PathogenTestDto.TUBE_NIL); - - final String tubeNilFieldValue = (String) tubeNilField.getValue(); - if(tubeNilFieldValue == null) { - // if there is no value we don't care about the checkbox value - return; - } - Float tubeNilNewValue = null; - try { - tubeNilNewValue = Float.valueOf(tubeNilFieldValue); - } catch (NumberFormatException ex) { - // if it's not a number we don't care about the value - tubeNilField.clear(); - return; - } - // if the checkbox is checked and the value is less than 10, we clear the field - if (checked && tubeNilNewValue < 10) { - tubeNilField.clear(); - return; - } - // if the checkbox is unchecked and the value is greater than or equal to 10, we clear the field - if(!checked && tubeNilNewValue >= 10) { - tubeNilField.clear(); - return; - } - }).build(), - FieldConfiguration.builder(PathogenTestDto.TUBE_AG_TB1_GT10).valueChangeListener(event -> { - final Object propertySingleValue = event.getProperty().getValue() instanceof Collection - ? ((Collection) event.getProperty().getValue()).stream().findFirst().orElse(null) - : event.getProperty().getValue(); - final Float tubeAgTb1Value = getValue().getTubeAgTb1(); - - // we are called for a new entry or initial calls - if(propertySingleValue == null && tubeAgTb1Value == null) { - final NullableOptionGroup tubeAgTb1Gt10Field = getField(PathogenTestDto.TUBE_AG_TB1_GT10); - tubeAgTb1Gt10Field.select(false); - return; - } - final boolean checked = Boolean.TRUE.equals(propertySingleValue); - final Field tubeAgTb1Field = getField(PathogenTestDto.TUBE_AG_TB1); - - final String tubeAgTb1FieldValue = (String) tubeAgTb1Field.getValue(); - if(tubeAgTb1FieldValue == null) { - // if there is no value we don't care about the checkbox value - return; - } - Float tubeAgTb1NewValue = null; - try { - tubeAgTb1NewValue = Float.valueOf(tubeAgTb1FieldValue); - } catch (NumberFormatException ex) { - // if it's not a number we don't care about the value - tubeAgTb1Field.clear(); - return; - } - // if the checkbox is checked and the value is less than or equal to 10, we clear the field - if (checked && tubeAgTb1NewValue <= 10) { - tubeAgTb1Field.clear(); - return; - } - // if the checkbox is unchecked and the value is greater than 10, we clear the field - if(!checked && tubeAgTb1NewValue > 10) { - tubeAgTb1Field.clear(); - return; - } - }).build(), - FieldConfiguration.builder(PathogenTestDto.TUBE_AG_TB2_GT10).valueChangeListener(event -> { - final Object propertySingleValue = event.getProperty().getValue() instanceof Collection - ? ((Collection) event.getProperty().getValue()).stream().findFirst().orElse(null) - : event.getProperty().getValue(); - final Float tubeAgTb2Value = getValue().getTubeAgTb2(); - - // we are called for a new entry or initial calls - if(propertySingleValue == null && tubeAgTb2Value == null) { - final NullableOptionGroup tubeAgTb2Gt10Field = getField(PathogenTestDto.TUBE_AG_TB2_GT10); - tubeAgTb2Gt10Field.select(false); - return; - } - final boolean checked = Boolean.TRUE.equals(propertySingleValue); - final Field tubeAgTb2Field = getField(PathogenTestDto.TUBE_AG_TB2); - - final String tubeAgTb2FieldValue = (String) tubeAgTb2Field.getValue(); - if(tubeAgTb2FieldValue == null) { - // if there is no value we don't care about the checkbox value - return; - } - Float tubeAgTb2NewValue = null; - try { - tubeAgTb2NewValue = Float.valueOf(tubeAgTb2FieldValue); - } catch (NumberFormatException ex) { - // if it's not a number we don't care about the value - tubeAgTb2Field.clear(); - return; - } - // if the checkbox is checked and the value is less than or equal to 10, we clear the field - if (checked && tubeAgTb2NewValue <= 10) { - tubeAgTb2Field.clear(); - return; - } - // if the checkbox is unchecked and the value is greater than 10, we clear the field - if(!checked && tubeAgTb2NewValue > 10) { - tubeAgTb2Field.clear(); - return; - } - }).build(), - FieldConfiguration.builder(PathogenTestDto.TUBE_MITOGENE_GT10).valueChangeListener(event -> { - final Object propertySingleValue = event.getProperty().getValue() instanceof Collection - ? ((Collection) event.getProperty().getValue()).stream().findFirst().orElse(null) - : event.getProperty().getValue(); - final Float tubeMitogeneValue = getValue().getTubeMitogene(); - - // we are called for a new entry or initial calls - if(propertySingleValue == null && tubeMitogeneValue == null) { - final NullableOptionGroup tubeMitogeneGt10Field = getField(PathogenTestDto.TUBE_MITOGENE_GT10); - tubeMitogeneGt10Field.select(false); - return; - } - final boolean checked = Boolean.TRUE.equals(propertySingleValue); - final Field tubeMitogeneField = getField(PathogenTestDto.TUBE_MITOGENE); - - final String tubeMitogeneFieldValue = (String) tubeMitogeneField.getValue(); - if(tubeMitogeneFieldValue == null) { - // if there is no value we don't care about the checkbox value - return; - } - Float tubeMitogeneNewValue = null; - try { - tubeMitogeneNewValue = Float.valueOf(tubeMitogeneFieldValue); - } catch (NumberFormatException ex) { - // if it's not a number we don't care about the value - tubeMitogeneField.clear(); - return; - } - // if the checkbox is checked and the value is less than or equal to 10, we clear the field - if (checked && tubeMitogeneNewValue <= 10) { - tubeMitogeneField.clear(); - return; - } - // if the checkbox is unchecked and the value is greater than 10, we clear the field - if(!checked && tubeMitogeneNewValue > 10) { - tubeMitogeneField.clear(); - return; - } - }).build() - ); - //@formatter:on - - setVisibleClear( - false, - PathogenTestDto.TUBE_NIL, - PathogenTestDto.TUBE_NIL_GT10, - PathogenTestDto.TUBE_AG_TB1, - PathogenTestDto.TUBE_AG_TB1_GT10, - PathogenTestDto.TUBE_AG_TB2, - PathogenTestDto.TUBE_AG_TB2_GT10, - PathogenTestDto.TUBE_MITOGENE, - PathogenTestDto.TUBE_MITOGENE_GT10); - NullableOptionGroup testResultVerifiedField = addField(PathogenTestDto.TEST_RESULT_VERIFIED, NullableOptionGroup.class); addField(PathogenTestDto.PRELIMINARY).addStyleName(CssStyles.VSPACE_4); @@ -1133,6 +750,7 @@ protected void addFields() { } disease = latestDisease; updateDiseaseVariantField.accept(disease); + swapDiseaseSection(latestDisease); FieldHelper.updateItems( testTypeField, @@ -1141,11 +759,14 @@ protected void addFields() { PathogenTestType.class); if (FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { - FieldHelper.updateItems( - strainCallStatusField, - Arrays.asList(PathogenStrainCallStatus.values()), - FieldVisibilityCheckers.withDisease(disease), - PathogenStrainCallStatus.class); + ComboBox strainCallStatusField = getField(PathogenTestDto.STRAIN_CALL_STATUS); + if (strainCallStatusField != null) { + FieldHelper.updateItems( + strainCallStatusField, + Arrays.asList(PathogenStrainCallStatus.values()), + FieldVisibilityCheckers.withDisease(disease), + PathogenStrainCallStatus.class); + } updateDrugSusceptibilityFieldSpecifications((PathogenTestType) testTypeField.getValue(), disease); } @@ -1286,4 +907,207 @@ public void valueChange(com.vaadin.v7.data.Property.ValueChangeEvent event) { } } + + // ----------------------------------------------------------------------- + // Disease section swap support + // ----------------------------------------------------------------------- + + /** Replaces the active disease section with the one appropriate for the given disease. */ + private void swapDiseaseSection(Disease newDisease) { + DiseaseSectionLayout newSection = DiseaseSectionLayout.forDisease(newDisease); + if (newSection.getClass() == activeSection.getClass()) { + return; // same section type, nothing to swap + } + + activeSection.unbindFields(this); + activeSection = newSection; + diseaseSectionPanel.setTemplateContents(newSection.getHtmlLayout()); + newSection.bindFields(this); + } + + /** Package-private: adds a disease-section field to the nested diseaseSectionPanel layout. */ + F addSectionField(String propertyId, Class fieldType) { + F field = getFieldGroup().buildAndBind(propertyId, (Object) propertyId, fieldType); + formatField(field, propertyId); + field.setId(propertyId); + diseaseSectionPanel.addComponent(field, propertyId); + return field; + } + + /** Package-private: adds the DrugSusceptibilityForm to the disease section panel. */ + void addSectionDrugSusceptibilityField() { + drugSusceptibilityField = (DrugSusceptibilityForm) addField( + diseaseSectionPanel, + PathogenTestDto.DRUG_SUSCEPTIBILITY, + new DrugSusceptibilityForm( + de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers.getNoop(), + de.symeda.sormas.api.utils.fieldaccess.UiFieldAccessCheckers.getDefault(true, FacadeProvider.getConfigFacade().getCountryLocale()))); + drugSusceptibilityField.setCaption(null); + addToVisibleAllowedFields(drugSusceptibilityField); + } + + /** Package-private: adds all tube IGRA fields to the disease section panel. */ + void addSectionTubeFields() { + addFields( + diseaseSectionPanel, + FieldConfiguration.builder(PathogenTestDto.TUBE_NIL) + .validationMessageProperty(de.symeda.sormas.api.i18n.Validations.onlyNumbersAllowed) + .valueChangeListener(e -> handleTubeNilChange((String) e.getProperty().getValue())) + .build(), + FieldConfiguration.builder(PathogenTestDto.TUBE_AG_TB1) + .validationMessageProperty(de.symeda.sormas.api.i18n.Validations.onlyNumbersAllowed) + .valueChangeListener(e -> handleTubeAgTb1Change((String) e.getProperty().getValue())) + .build(), + FieldConfiguration.builder(PathogenTestDto.TUBE_AG_TB2) + .validationMessageProperty(de.symeda.sormas.api.i18n.Validations.onlyNumbersAllowed) + .valueChangeListener(e -> handleTubeAgTb2Change((String) e.getProperty().getValue())) + .build(), + FieldConfiguration.builder(PathogenTestDto.TUBE_MITOGENE) + .validationMessageProperty(de.symeda.sormas.api.i18n.Validations.onlyNumbersAllowed) + .valueChangeListener(e -> handleTubeMitogeneChange((String) e.getProperty().getValue())) + .build()); + addFields( + diseaseSectionPanel, + FieldConfiguration.builder(PathogenTestDto.TUBE_NIL_GT10).valueChangeListener(e -> handleTubeNilGt10Change(e)).build(), + FieldConfiguration.builder(PathogenTestDto.TUBE_AG_TB1_GT10).valueChangeListener(e -> handleTubeAgTb1Gt10Change(e)).build(), + FieldConfiguration.builder(PathogenTestDto.TUBE_AG_TB2_GT10).valueChangeListener(e -> handleTubeAgTb2Gt10Change(e)).build(), + FieldConfiguration.builder(PathogenTestDto.TUBE_MITOGENE_GT10).valueChangeListener(e -> handleTubeMitogeneGt10Change(e)).build()); + + setVisibleClear( + false, + PathogenTestDto.TUBE_NIL, + PathogenTestDto.TUBE_NIL_GT10, + PathogenTestDto.TUBE_AG_TB1, + PathogenTestDto.TUBE_AG_TB1_GT10, + PathogenTestDto.TUBE_AG_TB2, + PathogenTestDto.TUBE_AG_TB2_GT10, + PathogenTestDto.TUBE_MITOGENE, + PathogenTestDto.TUBE_MITOGENE_GT10); + } + + /** Package-private: removes a field from both the disease section panel and the field group. */ + void removeSectionField(String propertyId) { + com.vaadin.v7.ui.Field field = getField(propertyId); + if (field != null) { + // Unbind first so listeners on sibling fields don't NPE when they fire during clear() + getFieldGroup().unbind(field); + diseaseSectionPanel.removeComponent(field); + } + } + + /** Returns the currently selected disease — used by section implementations. */ + Disease getCurrentDisease() { + return disease; + } + + // Tube field helpers — delegates from listeners in the original addFields() block + private void handleTubeNilChange(String val) { + NullableOptionGroup gt10 = getField(PathogenTestDto.TUBE_NIL_GT10); + if (gt10 == null) + return; + if (val == null) { + gt10.select(false); + return; + } + try { + gt10.select(Float.parseFloat(val) > 10); + } catch (NumberFormatException e) { + getField(PathogenTestDto.TUBE_NIL).clear(); + gt10.select(false); + } + } + + private void handleTubeAgTb1Change(String val) { + NullableOptionGroup gt10 = getField(PathogenTestDto.TUBE_AG_TB1_GT10); + if (gt10 == null) + return; + if (val == null) { + gt10.select(false); + return; + } + try { + gt10.select(Float.parseFloat(val) > 10); + } catch (NumberFormatException e) { + getField(PathogenTestDto.TUBE_AG_TB1).clear(); + gt10.select(false); + } + } + + private void handleTubeAgTb2Change(String val) { + NullableOptionGroup gt10 = getField(PathogenTestDto.TUBE_AG_TB2_GT10); + if (gt10 == null) + return; + if (val == null) { + gt10.select(false); + return; + } + try { + gt10.select(Float.parseFloat(val) > 10); + } catch (NumberFormatException e) { + getField(PathogenTestDto.TUBE_AG_TB2).clear(); + gt10.select(false); + } + } + + private void handleTubeMitogeneChange(String val) { + NullableOptionGroup gt10 = getField(PathogenTestDto.TUBE_MITOGENE_GT10); + if (gt10 == null) + return; + if (val == null) { + gt10.select(false); + return; + } + try { + gt10.select(Float.parseFloat(val) > 10); + } catch (NumberFormatException e) { + getField(PathogenTestDto.TUBE_MITOGENE).clear(); + gt10.select(false); + } + } + + private void handleTubeNilGt10Change(com.vaadin.v7.data.Property.ValueChangeEvent e) { + Object v = e.getProperty().getValue() instanceof Collection + ? ((Collection) e.getProperty().getValue()).stream().findFirst().orElse(null) + : e.getProperty().getValue(); + handleGt10CheckboxChange(v, getField(PathogenTestDto.TUBE_NIL)); + } + + private void handleTubeAgTb1Gt10Change(com.vaadin.v7.data.Property.ValueChangeEvent e) { + Object v = e.getProperty().getValue() instanceof Collection + ? ((Collection) e.getProperty().getValue()).stream().findFirst().orElse(null) + : e.getProperty().getValue(); + handleGt10CheckboxChange(v, getField(PathogenTestDto.TUBE_AG_TB1)); + } + + private void handleTubeAgTb2Gt10Change(com.vaadin.v7.data.Property.ValueChangeEvent e) { + Object v = e.getProperty().getValue() instanceof Collection + ? ((Collection) e.getProperty().getValue()).stream().findFirst().orElse(null) + : e.getProperty().getValue(); + handleGt10CheckboxChange(v, getField(PathogenTestDto.TUBE_AG_TB2)); + } + + private void handleTubeMitogeneGt10Change(com.vaadin.v7.data.Property.ValueChangeEvent e) { + Object v = e.getProperty().getValue() instanceof Collection + ? ((Collection) e.getProperty().getValue()).stream().findFirst().orElse(null) + : e.getProperty().getValue(); + handleGt10CheckboxChange(v, getField(PathogenTestDto.TUBE_MITOGENE)); + } + + private void handleGt10CheckboxChange(Object singleValue, com.vaadin.v7.ui.Field numericField) { + if (numericField == null) + return; + String numVal = (String) numericField.getValue(); + if (singleValue == null || numVal == null) + return; + boolean checked = Boolean.TRUE.equals(singleValue); + try { + float f = Float.valueOf(numVal); + if (checked && f <= 10) + numericField.clear(); + else if (!checked && f > 10) + numericField.clear(); + } catch (NumberFormatException ex) { + numericField.clear(); + } + } } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TuberculosisDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TuberculosisDiseaseSectionLayout.java new file mode 100644 index 00000000000..95560caa24f --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TuberculosisDiseaseSectionLayout.java @@ -0,0 +1,149 @@ +/******************************************************************************* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2018 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * + * 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.samples; + +import static de.symeda.sormas.ui.utils.LayoutUtil.fluidRowLocs; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.vaadin.v7.ui.AbstractSelect.ItemCaptionMode; +import com.vaadin.v7.ui.ComboBox; +import com.vaadin.v7.ui.TextField; + +import de.symeda.sormas.api.CountryHelper; +import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.FacadeProvider; +import de.symeda.sormas.api.sample.PathogenStrainCallStatus; +import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.api.sample.PathogenTestType; +import de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers; +import de.symeda.sormas.ui.utils.FieldHelper; +import de.symeda.sormas.ui.utils.NullableOptionGroup; + +/** + * Disease-specific section for TUBERCULOSIS and LATENT_TUBERCULOSIS. + * Injects TB fields into PathogenTestForm's "diseaseSectionLoc" slot and removes them on unbind. + */ +public class TuberculosisDiseaseSectionLayout implements DiseaseSectionLayout { + + //@formatter:off + private static final String HTML = + fluidRowLocs(PathogenTestDto.RIFAMPICIN_RESISTANT, PathogenTestDto.ISONIAZID_RESISTANT, "", "") + + fluidRowLocs(PathogenTestDto.TEST_SCALE, "") + + fluidRowLocs(PathogenTestDto.STRAIN_CALL_STATUS, "") + + fluidRowLocs(PathogenTestDto.SPECIE, "") + + fluidRowLocs(PathogenTestDto.PATTERN_PROFILE, "") + + fluidRowLocs(PathogenTestDto.DRUG_SUSCEPTIBILITY) + + fluidRowLocs(PathogenTestDto.TUBE_NIL, PathogenTestDto.TUBE_NIL_GT10) + + fluidRowLocs(PathogenTestDto.TUBE_AG_TB1, PathogenTestDto.TUBE_AG_TB1_GT10) + + fluidRowLocs(PathogenTestDto.TUBE_AG_TB2, PathogenTestDto.TUBE_AG_TB2_GT10) + + fluidRowLocs(PathogenTestDto.TUBE_MITOGENE, PathogenTestDto.TUBE_MITOGENE_GT10); + //@formatter:on + + private static final String[] FIELD_IDS = { + PathogenTestDto.RIFAMPICIN_RESISTANT, + PathogenTestDto.ISONIAZID_RESISTANT, + PathogenTestDto.TEST_SCALE, + PathogenTestDto.STRAIN_CALL_STATUS, + PathogenTestDto.SPECIE, + PathogenTestDto.PATTERN_PROFILE, + PathogenTestDto.DRUG_SUSCEPTIBILITY, + PathogenTestDto.TUBE_NIL, + PathogenTestDto.TUBE_NIL_GT10, + PathogenTestDto.TUBE_AG_TB1, + PathogenTestDto.TUBE_AG_TB1_GT10, + PathogenTestDto.TUBE_AG_TB2, + PathogenTestDto.TUBE_AG_TB2_GT10, + PathogenTestDto.TUBE_MITOGENE, + PathogenTestDto.TUBE_MITOGENE_GT10, }; + + @Override + public String getHtmlLayout() { + return HTML; + } + + @Override + public void bindFields(PathogenTestForm form) { + NullableOptionGroup rifampicinResistant = form.addSectionField(PathogenTestDto.RIFAMPICIN_RESISTANT, NullableOptionGroup.class); + rifampicinResistant.setVisible(false); + + NullableOptionGroup isoniazidResistant = form.addSectionField(PathogenTestDto.ISONIAZID_RESISTANT, NullableOptionGroup.class); + isoniazidResistant.setVisible(false); + + ComboBox testScale = form.addSectionField(PathogenTestDto.TEST_SCALE, ComboBox.class); + testScale.setVisible(false); + + ComboBox strainCallStatus = form.addSectionField(PathogenTestDto.STRAIN_CALL_STATUS, ComboBox.class); + strainCallStatus.setItemCaptionMode(ItemCaptionMode.ID_TOSTRING); + strainCallStatus.setVisible(false); + + ComboBox specie = form.addSectionField(PathogenTestDto.SPECIE, ComboBox.class); + specie.setVisible(false); + + TextField patternProfile = form.addSectionField(PathogenTestDto.PATTERN_PROFILE, TextField.class); + patternProfile.setVisible(false); + + form.addSectionDrugSusceptibilityField(); + + form.addSectionTubeFields(); + + // Luxembourg: wire visibility rules + if (FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { + FieldHelper.setVisibleWhen( + form.getFieldGroup(), + PathogenTestDto.RIFAMPICIN_RESISTANT, + PathogenTestForm.RIFAMPICIN_RESISTANT_VISIBILITY_CONDITIONS, + true); + FieldHelper.setVisibleWhen(form.getFieldGroup(), PathogenTestDto.TEST_SCALE, PathogenTestForm.TEST_SCALE_VISIBILITY_CONDITIONS, true); + FieldHelper.setVisibleWhen( + form.getFieldGroup(), + PathogenTestDto.STRAIN_CALL_STATUS, + PathogenTestForm.STRAIN_CALL_STATUS_VISIBILITY_CONDITIONS, + true); + FieldHelper.setVisibleWhen(form.getFieldGroup(), PathogenTestDto.SPECIE, PathogenTestForm.SPECIE_VISIBILITY_CONDITIONS, true); + + Map> miruCode = new HashMap<>(); + miruCode.put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.TUBERCULOSIS, Disease.LATENT_TUBERCULOSIS)); + miruCode.put(PathogenTestDto.TEST_TYPE, Arrays.asList(PathogenTestType.MIRU_PATTERN_CODE)); + FieldHelper.setVisibleWhen(form.getFieldGroup(), PathogenTestDto.PATTERN_PROFILE, miruCode, true); + + FieldHelper.updateItems( + strainCallStatus, + Arrays.asList(PathogenStrainCallStatus.values()), + FieldVisibilityCheckers.withDisease(form.getCurrentDisease()), + PathogenStrainCallStatus.class); + } + } + + @Override + public void unbindFields(PathogenTestForm form) { + for (String id : FIELD_IDS) { + form.removeSectionField(id); + } + } + + @Override + public Disease[] getDiseases() { + return new Disease[] { + Disease.TUBERCULOSIS, + Disease.LATENT_TUBERCULOSIS }; + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/FieldHelper.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/FieldHelper.java index f016b8bbf47..86534e03591 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/FieldHelper.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/FieldHelper.java @@ -364,6 +364,7 @@ private static void onValueChangedSetVisible( for (Object targetPropertyId : targetPropertyIds) { @SuppressWarnings("rawtypes") Field targetField = fieldGroup.getField(targetPropertyId); + if (targetField == null) continue; // field was unbound (e.g. disease section swap) targetField.setVisible(visible); if (!visible && clearOnHidden && targetField.getValue() != null) { targetField.clear(); From e18690c63a83a15562f1df06dcae5d164101f6f0 Mon Sep 17 00:00:00 2001 From: Harold Asiimwe Date: Mon, 2 Mar 2026 07:41:41 +0300 Subject: [PATCH 02/30] #13854 - cleans interface that receives only FieldGroup, CustomLayout, and Disease. --- .../samples/DefaultDiseaseSectionLayout.java | 15 ++- .../ui/samples/DiseaseSectionLayout.java | 32 +++-- .../sormas/ui/samples/PathogenTestForm.java | 6 +- .../TuberculosisDiseaseSectionLayout.java | 111 +++++++++++++----- 4 files changed, 124 insertions(+), 40 deletions(-) diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/DefaultDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/DefaultDiseaseSectionLayout.java index 172a0842305..e6e1154450f 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/DefaultDiseaseSectionLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/DefaultDiseaseSectionLayout.java @@ -17,6 +17,12 @@ *******************************************************************************/ package de.symeda.sormas.ui.samples; +import java.util.Collection; +import java.util.Collections; + +import com.vaadin.ui.CustomLayout; +import com.vaadin.v7.data.fieldgroup.FieldGroup; + import de.symeda.sormas.api.Disease; /** @@ -30,12 +36,17 @@ public String getHtmlLayout() { } @Override - public void bindFields(PathogenTestForm form) { + public Collection getFieldIds() { + return Collections.emptyList(); + } + + @Override + public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease) { // no disease-specific fields } @Override - public void unbindFields(PathogenTestForm form) { + public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { // nothing to remove } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/DiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/DiseaseSectionLayout.java index 7df756e5ff6..ce975438c47 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/DiseaseSectionLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/DiseaseSectionLayout.java @@ -17,7 +17,15 @@ *******************************************************************************/ package de.symeda.sormas.ui.samples; +import java.util.Collection; + +import com.vaadin.ui.CustomLayout; +import com.vaadin.v7.data.fieldgroup.FieldGroup; +import com.vaadin.v7.ui.AbstractField; + import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.sample.PathogenTestResultType; +import de.symeda.sormas.api.sample.PathogenTestType; /** * Encapsulates the disease-specific portion of PathogenTestForm. @@ -28,8 +36,8 @@ * Lifecycle: *
      *
    1. {@link #getHtmlLayout()} is called once to build the section's CustomLayout template.
    2. - *
    3. {@link #bindFields(PathogenTestForm)} is called to add fields to the form.
    4. - *
    5. {@link #unbindFields(PathogenTestForm)} is called before a new section replaces this one.
    6. + *
    7. {@link #bindFields(FieldGroup, CustomLayout, Disease)} is called to add fields.
    8. + *
    9. {@link #unbindFields(FieldGroup, CustomLayout)} is called before a new section replaces this one.
    10. *
    */ public interface DiseaseSectionLayout { @@ -40,20 +48,30 @@ public interface DiseaseSectionLayout { */ String getHtmlLayout(); + /** Returns the property IDs of all fields bound by {@link #bindFields}. */ + Collection getFieldIds(); + /** - * Adds this section's fields to the host form's layout and field group. + * Adds this section's fields to the given field group and panel. * Called once after the section's CustomLayout has been installed. */ - void bindFields(PathogenTestForm form); + void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease); /** - * Removes this section's fields from the host form's layout and field group, + * Removes this section's fields from the field group and panel, * clearing their values so they don't bleed into the saved DTO. * Called before a new section replaces this one. */ - void unbindFields(PathogenTestForm form); + void unbindFields(FieldGroup fieldGroup, CustomLayout panel); + + /** + * Called when the test type changes so sections can update field visibility or + * auto-set the test result. No-op by default. + */ + default void onTestTypeChanged(PathogenTestType testType, Disease disease, AbstractField testResultField) { + } - /** Returns the disease(s) this section handles, for logging/debugging. */ + /** Returns the disease(s) this section handles. */ Disease[] getDiseases(); /** Factory: returns the correct section implementation for the given disease. */ diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java index a75087dfa1f..434b2196f07 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java @@ -534,7 +534,7 @@ protected void addFields() { // Bind the initial disease section (default = no-op; swapped via swapDiseaseSection() on disease change) activeSection = DiseaseSectionLayout.forDisease(disease); diseaseSectionPanel.setTemplateContents(activeSection.getHtmlLayout()); - activeSection.bindFields(this); + activeSection.bindFields(getFieldGroup(), diseaseSectionPanel, disease); seroTypeTF.setVisible(false); @@ -919,10 +919,10 @@ private void swapDiseaseSection(Disease newDisease) { return; // same section type, nothing to swap } - activeSection.unbindFields(this); + activeSection.unbindFields(getFieldGroup(), diseaseSectionPanel); activeSection = newSection; diseaseSectionPanel.setTemplateContents(newSection.getHtmlLayout()); - newSection.bindFields(this); + newSection.bindFields(getFieldGroup(), diseaseSectionPanel, newDisease); } /** Package-private: adds a disease-section field to the nested diseaseSectionPanel layout. */ diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TuberculosisDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TuberculosisDiseaseSectionLayout.java index 95560caa24f..08dfe475974 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TuberculosisDiseaseSectionLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TuberculosisDiseaseSectionLayout.java @@ -20,10 +20,14 @@ import static de.symeda.sormas.ui.utils.LayoutUtil.fluidRowLocs; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import com.vaadin.ui.CustomLayout; +import com.vaadin.v7.data.fieldgroup.FieldGroup; +import com.vaadin.v7.ui.AbstractField; import com.vaadin.v7.ui.AbstractSelect.ItemCaptionMode; import com.vaadin.v7.ui.ComboBox; import com.vaadin.v7.ui.TextField; @@ -33,8 +37,11 @@ import de.symeda.sormas.api.FacadeProvider; import de.symeda.sormas.api.sample.PathogenStrainCallStatus; import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.api.sample.PathogenTestResultType; import de.symeda.sormas.api.sample.PathogenTestType; +import de.symeda.sormas.api.utils.fieldaccess.UiFieldAccessCheckers; import de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers; +import de.symeda.sormas.ui.therapy.DrugSusceptibilityForm; import de.symeda.sormas.ui.utils.FieldHelper; import de.symeda.sormas.ui.utils.NullableOptionGroup; @@ -58,7 +65,7 @@ public class TuberculosisDiseaseSectionLayout implements DiseaseSectionLayout { fluidRowLocs(PathogenTestDto.TUBE_MITOGENE, PathogenTestDto.TUBE_MITOGENE_GT10); //@formatter:on - private static final String[] FIELD_IDS = { + private static final List FIELD_IDS = Arrays.asList( PathogenTestDto.RIFAMPICIN_RESISTANT, PathogenTestDto.ISONIAZID_RESISTANT, PathogenTestDto.TEST_SCALE, @@ -73,7 +80,7 @@ public class TuberculosisDiseaseSectionLayout implements DiseaseSectionLayout { PathogenTestDto.TUBE_AG_TB2, PathogenTestDto.TUBE_AG_TB2_GT10, PathogenTestDto.TUBE_MITOGENE, - PathogenTestDto.TUBE_MITOGENE_GT10, }; + PathogenTestDto.TUBE_MITOGENE_GT10); @Override public String getHtmlLayout() { @@ -81,62 +88,103 @@ public String getHtmlLayout() { } @Override - public void bindFields(PathogenTestForm form) { - NullableOptionGroup rifampicinResistant = form.addSectionField(PathogenTestDto.RIFAMPICIN_RESISTANT, NullableOptionGroup.class); + public Collection getFieldIds() { + return FIELD_IDS; + } + + @Override + public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease) { + NullableOptionGroup rifampicinResistant = buildAndAdd(fieldGroup, panel, PathogenTestDto.RIFAMPICIN_RESISTANT, NullableOptionGroup.class); rifampicinResistant.setVisible(false); - NullableOptionGroup isoniazidResistant = form.addSectionField(PathogenTestDto.ISONIAZID_RESISTANT, NullableOptionGroup.class); + NullableOptionGroup isoniazidResistant = buildAndAdd(fieldGroup, panel, PathogenTestDto.ISONIAZID_RESISTANT, NullableOptionGroup.class); isoniazidResistant.setVisible(false); - ComboBox testScale = form.addSectionField(PathogenTestDto.TEST_SCALE, ComboBox.class); + ComboBox testScale = buildAndAdd(fieldGroup, panel, PathogenTestDto.TEST_SCALE, ComboBox.class); testScale.setVisible(false); - ComboBox strainCallStatus = form.addSectionField(PathogenTestDto.STRAIN_CALL_STATUS, ComboBox.class); + ComboBox strainCallStatus = buildAndAdd(fieldGroup, panel, PathogenTestDto.STRAIN_CALL_STATUS, ComboBox.class); strainCallStatus.setItemCaptionMode(ItemCaptionMode.ID_TOSTRING); strainCallStatus.setVisible(false); - ComboBox specie = form.addSectionField(PathogenTestDto.SPECIE, ComboBox.class); + ComboBox specie = buildAndAdd(fieldGroup, panel, PathogenTestDto.SPECIE, ComboBox.class); specie.setVisible(false); - TextField patternProfile = form.addSectionField(PathogenTestDto.PATTERN_PROFILE, TextField.class); + TextField patternProfile = buildAndAdd(fieldGroup, panel, PathogenTestDto.PATTERN_PROFILE, TextField.class); patternProfile.setVisible(false); - form.addSectionDrugSusceptibilityField(); - - form.addSectionTubeFields(); + DrugSusceptibilityForm drugSusceptibility = new DrugSusceptibilityForm( + FieldVisibilityCheckers.getNoop(), + UiFieldAccessCheckers.getDefault(true, FacadeProvider.getConfigFacade().getCountryLocale())); + drugSusceptibility.setCaption(null); + fieldGroup.bind(drugSusceptibility, PathogenTestDto.DRUG_SUSCEPTIBILITY); + panel.addComponent(drugSusceptibility, PathogenTestDto.DRUG_SUSCEPTIBILITY); + + // Tube fields — stub: built and added hidden + for (String tubeId : Arrays + .asList(PathogenTestDto.TUBE_NIL, PathogenTestDto.TUBE_AG_TB1, PathogenTestDto.TUBE_AG_TB2, PathogenTestDto.TUBE_MITOGENE)) { + TextField tubeField = buildAndAdd(fieldGroup, panel, tubeId, TextField.class); + tubeField.setVisible(false); + } + for (String gt10Id : Arrays.asList( + PathogenTestDto.TUBE_NIL_GT10, + PathogenTestDto.TUBE_AG_TB1_GT10, + PathogenTestDto.TUBE_AG_TB2_GT10, + PathogenTestDto.TUBE_MITOGENE_GT10)) { + NullableOptionGroup gt10Field = buildAndAdd(fieldGroup, panel, gt10Id, NullableOptionGroup.class); + gt10Field.setVisible(false); + } // Luxembourg: wire visibility rules if (FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { - FieldHelper.setVisibleWhen( - form.getFieldGroup(), - PathogenTestDto.RIFAMPICIN_RESISTANT, - PathogenTestForm.RIFAMPICIN_RESISTANT_VISIBILITY_CONDITIONS, - true); - FieldHelper.setVisibleWhen(form.getFieldGroup(), PathogenTestDto.TEST_SCALE, PathogenTestForm.TEST_SCALE_VISIBILITY_CONDITIONS, true); - FieldHelper.setVisibleWhen( - form.getFieldGroup(), - PathogenTestDto.STRAIN_CALL_STATUS, - PathogenTestForm.STRAIN_CALL_STATUS_VISIBILITY_CONDITIONS, - true); - FieldHelper.setVisibleWhen(form.getFieldGroup(), PathogenTestDto.SPECIE, PathogenTestForm.SPECIE_VISIBILITY_CONDITIONS, true); + FieldHelper + .setVisibleWhen(fieldGroup, PathogenTestDto.RIFAMPICIN_RESISTANT, PathogenTestForm.RIFAMPICIN_RESISTANT_VISIBILITY_CONDITIONS, true); + FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.TEST_SCALE, PathogenTestForm.TEST_SCALE_VISIBILITY_CONDITIONS, true); + FieldHelper + .setVisibleWhen(fieldGroup, PathogenTestDto.STRAIN_CALL_STATUS, PathogenTestForm.STRAIN_CALL_STATUS_VISIBILITY_CONDITIONS, true); + FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.SPECIE, PathogenTestForm.SPECIE_VISIBILITY_CONDITIONS, true); Map> miruCode = new HashMap<>(); miruCode.put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.TUBERCULOSIS, Disease.LATENT_TUBERCULOSIS)); miruCode.put(PathogenTestDto.TEST_TYPE, Arrays.asList(PathogenTestType.MIRU_PATTERN_CODE)); - FieldHelper.setVisibleWhen(form.getFieldGroup(), PathogenTestDto.PATTERN_PROFILE, miruCode, true); + FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.PATTERN_PROFILE, miruCode, true); FieldHelper.updateItems( strainCallStatus, Arrays.asList(PathogenStrainCallStatus.values()), - FieldVisibilityCheckers.withDisease(form.getCurrentDisease()), + FieldVisibilityCheckers.withDisease(disease), PathogenStrainCallStatus.class); } } @Override - public void unbindFields(PathogenTestForm form) { + public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { for (String id : FIELD_IDS) { - form.removeSectionField(id); + com.vaadin.v7.ui.Field field = fieldGroup.getField(id); + if (field != null) { + fieldGroup.unbind(field); + panel.removeComponent(field); + } + } + } + + @Override + public void onTestTypeChanged(PathogenTestType testType, Disease disease, AbstractField testResultField) { + if (testType == null) { + return; + } + + if (FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { + boolean isTb = disease == Disease.TUBERCULOSIS || disease == Disease.LATENT_TUBERCULOSIS; + + if (isTb + && (testType == PathogenTestType.BEIJINGGENOTYPING + || testType == PathogenTestType.MIRU_PATTERN_CODE + || testType == PathogenTestType.ANTIBIOTIC_SUSCEPTIBILITY)) { + testResultField.setValue(PathogenTestResultType.NOT_APPLICABLE); + } else if (isTb && testType == PathogenTestType.SPOLIGOTYPING) { + testResultField.setValue(PathogenTestResultType.POSITIVE); + } } } @@ -146,4 +194,11 @@ public Disease[] getDiseases() { Disease.TUBERCULOSIS, Disease.LATENT_TUBERCULOSIS }; } + + private > F buildAndAdd(FieldGroup fieldGroup, CustomLayout panel, String propertyId, Class fieldType) { + F field = fieldGroup.buildAndBind(propertyId, (Object) propertyId, fieldType); + field.setId(propertyId); + panel.addComponent(field, propertyId); + return field; + } } From 08bf2c87d8c244940cb09e86b8e16262018bd76b Mon Sep 17 00:00:00 2001 From: Harold Asiimwe Date: Mon, 2 Mar 2026 07:57:18 +0300 Subject: [PATCH 03/30] #13855 - Refactor TuberculosisDiseaseSectionLayout --- .../TuberculosisDiseaseSectionLayout.java | 172 ++++++++++++++---- 1 file changed, 135 insertions(+), 37 deletions(-) diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TuberculosisDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TuberculosisDiseaseSectionLayout.java index 08dfe475974..d2826c30d2c 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TuberculosisDiseaseSectionLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TuberculosisDiseaseSectionLayout.java @@ -26,10 +26,12 @@ import java.util.Map; import com.vaadin.ui.CustomLayout; +import com.vaadin.v7.data.Property; import com.vaadin.v7.data.fieldgroup.FieldGroup; import com.vaadin.v7.ui.AbstractField; import com.vaadin.v7.ui.AbstractSelect.ItemCaptionMode; import com.vaadin.v7.ui.ComboBox; +import com.vaadin.v7.ui.Field; import com.vaadin.v7.ui.TextField; import de.symeda.sormas.api.CountryHelper; @@ -47,7 +49,7 @@ /** * Disease-specific section for TUBERCULOSIS and LATENT_TUBERCULOSIS. - * Injects TB fields into PathogenTestForm's "diseaseSectionLoc" slot and removes them on unbind. + * Fully self-contained: no dependency on PathogenTestForm. */ public class TuberculosisDiseaseSectionLayout implements DiseaseSectionLayout { @@ -82,6 +84,15 @@ public class TuberculosisDiseaseSectionLayout implements DiseaseSectionLayout { PathogenTestDto.TUBE_MITOGENE, PathogenTestDto.TUBE_MITOGENE_GT10); + // Held for onTestTypeChanged and unbindFields + private DrugSusceptibilityForm drugSusceptibilityField; + private List tubeFieldIds; + + // Listeners registered on shared FieldGroup source fields — removed in unbindFields + private Property.ValueChangeListener testTypeListener; + private Property.ValueChangeListener testResultListener; + private Property.ValueChangeListener diseaseListener; + @Override public String getHtmlLayout() { return HTML; @@ -113,78 +124,165 @@ public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease diseas TextField patternProfile = buildAndAdd(fieldGroup, panel, PathogenTestDto.PATTERN_PROFILE, TextField.class); patternProfile.setVisible(false); - DrugSusceptibilityForm drugSusceptibility = new DrugSusceptibilityForm( + drugSusceptibilityField = new DrugSusceptibilityForm( FieldVisibilityCheckers.getNoop(), UiFieldAccessCheckers.getDefault(true, FacadeProvider.getConfigFacade().getCountryLocale())); - drugSusceptibility.setCaption(null); - fieldGroup.bind(drugSusceptibility, PathogenTestDto.DRUG_SUSCEPTIBILITY); - panel.addComponent(drugSusceptibility, PathogenTestDto.DRUG_SUSCEPTIBILITY); + drugSusceptibilityField.setCaption(null); + fieldGroup.bind(drugSusceptibilityField, PathogenTestDto.DRUG_SUSCEPTIBILITY); + panel.addComponent(drugSusceptibilityField, PathogenTestDto.DRUG_SUSCEPTIBILITY); + + // Tube fields — stub: built and added hidden; TubeFieldHandler wired in Phase 6 + tubeFieldIds = Arrays.asList( + PathogenTestDto.TUBE_NIL, + PathogenTestDto.TUBE_NIL_GT10, + PathogenTestDto.TUBE_AG_TB1, + PathogenTestDto.TUBE_AG_TB1_GT10, + PathogenTestDto.TUBE_AG_TB2, + PathogenTestDto.TUBE_AG_TB2_GT10, + PathogenTestDto.TUBE_MITOGENE, + PathogenTestDto.TUBE_MITOGENE_GT10); - // Tube fields — stub: built and added hidden for (String tubeId : Arrays .asList(PathogenTestDto.TUBE_NIL, PathogenTestDto.TUBE_AG_TB1, PathogenTestDto.TUBE_AG_TB2, PathogenTestDto.TUBE_MITOGENE)) { - TextField tubeField = buildAndAdd(fieldGroup, panel, tubeId, TextField.class); - tubeField.setVisible(false); + buildAndAdd(fieldGroup, panel, tubeId, TextField.class).setVisible(false); } for (String gt10Id : Arrays.asList( PathogenTestDto.TUBE_NIL_GT10, PathogenTestDto.TUBE_AG_TB1_GT10, PathogenTestDto.TUBE_AG_TB2_GT10, PathogenTestDto.TUBE_MITOGENE_GT10)) { - NullableOptionGroup gt10Field = buildAndAdd(fieldGroup, panel, gt10Id, NullableOptionGroup.class); - gt10Field.setVisible(false); + buildAndAdd(fieldGroup, panel, gt10Id, NullableOptionGroup.class).setVisible(false); } - // Luxembourg: wire visibility rules if (FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { - FieldHelper - .setVisibleWhen(fieldGroup, PathogenTestDto.RIFAMPICIN_RESISTANT, PathogenTestForm.RIFAMPICIN_RESISTANT_VISIBILITY_CONDITIONS, true); - FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.TEST_SCALE, PathogenTestForm.TEST_SCALE_VISIBILITY_CONDITIONS, true); - FieldHelper - .setVisibleWhen(fieldGroup, PathogenTestDto.STRAIN_CALL_STATUS, PathogenTestForm.STRAIN_CALL_STATUS_VISIBILITY_CONDITIONS, true); - FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.SPECIE, PathogenTestForm.SPECIE_VISIBILITY_CONDITIONS, true); - - Map> miruCode = new HashMap<>(); - miruCode.put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.TUBERCULOSIS, Disease.LATENT_TUBERCULOSIS)); - miruCode.put(PathogenTestDto.TEST_TYPE, Arrays.asList(PathogenTestType.MIRU_PATTERN_CODE)); - FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.PATTERN_PROFILE, miruCode, true); + bindLuxembourgVisibility(fieldGroup, strainCallStatus, disease); + } + } + private void bindLuxembourgVisibility(FieldGroup fieldGroup, ComboBox strainCallStatus, Disease disease) { + // Named listeners so they can be removed in unbindFields + testTypeListener = e -> { + Field testTypeField = fieldGroup.getField(PathogenTestDto.TEST_TYPE); + Field testResultField = fieldGroup.getField(PathogenTestDto.TEST_RESULT); + onValueChangedSetVisible(fieldGroup, testTypeField, testResultField); + }; + testResultListener = e -> { + Field testTypeField = fieldGroup.getField(PathogenTestDto.TEST_TYPE); + Field testResultField = fieldGroup.getField(PathogenTestDto.TEST_RESULT); + onValueChangedSetVisible(fieldGroup, testTypeField, testResultField); + }; + diseaseListener = e -> { + Field testTypeField = fieldGroup.getField(PathogenTestDto.TEST_TYPE); + Field testResultField = fieldGroup.getField(PathogenTestDto.TEST_RESULT); + onValueChangedSetVisible(fieldGroup, testTypeField, testResultField); + + Disease newDisease = (Disease) e.getProperty().getValue(); FieldHelper.updateItems( strainCallStatus, Arrays.asList(PathogenStrainCallStatus.values()), - FieldVisibilityCheckers.withDisease(disease), + FieldVisibilityCheckers.withDisease(newDisease), PathogenStrainCallStatus.class); + }; + + fieldGroup.getField(PathogenTestDto.TEST_TYPE).addValueChangeListener(testTypeListener); + fieldGroup.getField(PathogenTestDto.TEST_RESULT).addValueChangeListener(testResultListener); + fieldGroup.getField(PathogenTestDto.TESTED_DISEASE).addValueChangeListener(diseaseListener); + + // Wire standard multi-source visibility conditions + FieldHelper + .setVisibleWhen(fieldGroup, PathogenTestDto.RIFAMPICIN_RESISTANT, PathogenTestForm.RIFAMPICIN_RESISTANT_VISIBILITY_CONDITIONS, true); + FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.TEST_SCALE, PathogenTestForm.TEST_SCALE_VISIBILITY_CONDITIONS, true); + FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.STRAIN_CALL_STATUS, PathogenTestForm.STRAIN_CALL_STATUS_VISIBILITY_CONDITIONS, true); + FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.SPECIE, PathogenTestForm.SPECIE_VISIBILITY_CONDITIONS, true); + + Map> miruCode = new HashMap<>(); + miruCode.put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.TUBERCULOSIS, Disease.LATENT_TUBERCULOSIS)); + miruCode.put(PathogenTestDto.TEST_TYPE, Arrays.asList(PathogenTestType.MIRU_PATTERN_CODE)); + FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.PATTERN_PROFILE, miruCode, true); + + FieldHelper.updateItems( + strainCallStatus, + Arrays.asList(PathogenStrainCallStatus.values()), + FieldVisibilityCheckers.withDisease(disease), + PathogenStrainCallStatus.class); + + // Apply initial visibility + onValueChangedSetVisible(fieldGroup, fieldGroup.getField(PathogenTestDto.TEST_TYPE), fieldGroup.getField(PathogenTestDto.TEST_RESULT)); + } + + private void onValueChangedSetVisible(FieldGroup fieldGroup, Field testTypeField, Field testResultField) { + PathogenTestType testType = testTypeField != null ? (PathogenTestType) testTypeField.getValue() : null; + boolean showTubes = + testType == PathogenTestType.IGRA && FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG); + for (String tubeId : tubeFieldIds) { + Field f = fieldGroup.getField(tubeId); + if (f != null) { + f.setVisible(showTubes); + if (!showTubes) { + f.setValue(null); + } + } } } @Override public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { + // Remove named listeners from shared source fields before unbinding section fields + if (testTypeListener != null) { + Field f = fieldGroup.getField(PathogenTestDto.TEST_TYPE); + if (f != null) { + f.removeValueChangeListener(testTypeListener); + } + testTypeListener = null; + } + if (testResultListener != null) { + Field f = fieldGroup.getField(PathogenTestDto.TEST_RESULT); + if (f != null) { + f.removeValueChangeListener(testResultListener); + } + testResultListener = null; + } + if (diseaseListener != null) { + Field f = fieldGroup.getField(PathogenTestDto.TESTED_DISEASE); + if (f != null) { + f.removeValueChangeListener(diseaseListener); + } + diseaseListener = null; + } + for (String id : FIELD_IDS) { - com.vaadin.v7.ui.Field field = fieldGroup.getField(id); + Field field = fieldGroup.getField(id); if (field != null) { fieldGroup.unbind(field); panel.removeComponent(field); } } + + drugSusceptibilityField = null; + tubeFieldIds = null; } @Override public void onTestTypeChanged(PathogenTestType testType, Disease disease, AbstractField testResultField) { - if (testType == null) { + if (drugSusceptibilityField != null) { + drugSusceptibilityField.updateFieldsVisibility(disease, testType); + } + + if (testType == null || !FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { return; } - if (FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { - boolean isTb = disease == Disease.TUBERCULOSIS || disease == Disease.LATENT_TUBERCULOSIS; - - if (isTb - && (testType == PathogenTestType.BEIJINGGENOTYPING - || testType == PathogenTestType.MIRU_PATTERN_CODE - || testType == PathogenTestType.ANTIBIOTIC_SUSCEPTIBILITY)) { - testResultField.setValue(PathogenTestResultType.NOT_APPLICABLE); - } else if (isTb && testType == PathogenTestType.SPOLIGOTYPING) { - testResultField.setValue(PathogenTestResultType.POSITIVE); - } + boolean isTb = disease == Disease.TUBERCULOSIS || disease == Disease.LATENT_TUBERCULOSIS; + if (!isTb) { + return; + } + + if (testType == PathogenTestType.BEIJINGGENOTYPING + || testType == PathogenTestType.MIRU_PATTERN_CODE + || testType == PathogenTestType.ANTIBIOTIC_SUSCEPTIBILITY) { + testResultField.setValue(PathogenTestResultType.NOT_APPLICABLE); + } else if (testType == PathogenTestType.SPOLIGOTYPING) { + testResultField.setValue(PathogenTestResultType.POSITIVE); } } @@ -195,7 +293,7 @@ public Disease[] getDiseases() { Disease.LATENT_TUBERCULOSIS }; } - private > F buildAndAdd(FieldGroup fieldGroup, CustomLayout panel, String propertyId, Class fieldType) { + private > F buildAndAdd(FieldGroup fieldGroup, CustomLayout panel, String propertyId, Class fieldType) { F field = fieldGroup.buildAndBind(propertyId, (Object) propertyId, fieldType); field.setId(propertyId); panel.addComponent(field, propertyId); From 6f1d242fb0109fed650bf08e2cec087e02a7ca29 Mon Sep 17 00:00:00 2001 From: Harold Asiimwe Date: Mon, 2 Mar 2026 13:31:54 +0300 Subject: [PATCH 04/30] #13856 - Creates remaining DiseaseSectionLayout classes --- .../CoronavirusDiseaseSectionLayout.java | 66 +++++++++ ...CryptosporidiosisDiseaseSectionLayout.java | 105 ++++++++++++++ .../ui/samples/CsmDiseaseSectionLayout.java | 74 ++++++++++ .../ui/samples/DiseaseSectionLayout.java | 22 ++- .../ui/samples/ImiDiseaseSectionLayout.java | 122 ++++++++++++++++ .../ui/samples/IpiDiseaseSectionLayout.java | 136 ++++++++++++++++++ .../samples/MeaslesDiseaseSectionLayout.java | 106 ++++++++++++++ 7 files changed, 629 insertions(+), 2 deletions(-) create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/CoronavirusDiseaseSectionLayout.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/CryptosporidiosisDiseaseSectionLayout.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/CsmDiseaseSectionLayout.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/ImiDiseaseSectionLayout.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/IpiDiseaseSectionLayout.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/MeaslesDiseaseSectionLayout.java diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/CoronavirusDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/CoronavirusDiseaseSectionLayout.java new file mode 100644 index 00000000000..6a4f9caa078 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/CoronavirusDiseaseSectionLayout.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2018 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * + * 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.samples; + +import java.util.Collection; +import java.util.Collections; + +import com.vaadin.ui.CustomLayout; +import com.vaadin.v7.data.fieldgroup.FieldGroup; + +import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.ui.utils.FieldHelper; + +/** + * Disease section for CORONAVIRUS: wires PCR_TEST_SPECIFICATION visibility. + * Field lives in the common layout; this section owns the visibility registration. + */ +public class CoronavirusDiseaseSectionLayout implements DiseaseSectionLayout { + + private static final Collection FIELD_IDS = Collections.singletonList(PathogenTestDto.PCR_TEST_SPECIFICATION); + + @Override + public String getHtmlLayout() { + return ""; + } + + @Override + public Collection getFieldIds() { + return FIELD_IDS; + } + + @Override + public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease) { + FieldHelper + .setVisibleWhen(fieldGroup, PathogenTestDto.PCR_TEST_SPECIFICATION, PathogenTestForm.PCR_TEST_SPECIFICATION_VISIBILITY_CONDITIONS, true); + } + + @Override + public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { + // The PCR_TEST_SPECIFICATION visibility condition is keyed on TESTED_DISEASE=CORONAVIRUS, + // so it is inert when any other disease is active. No listener removal needed here; + // Phase 5 will consolidate all common-layout registrations. + } + + @Override + public Disease[] getDiseases() { + return new Disease[] { + Disease.CORONAVIRUS }; + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/CryptosporidiosisDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/CryptosporidiosisDiseaseSectionLayout.java new file mode 100644 index 00000000000..77dadd561e4 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/CryptosporidiosisDiseaseSectionLayout.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2018 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * + * 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.samples; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.vaadin.ui.CustomLayout; +import com.vaadin.v7.data.Property; +import com.vaadin.v7.data.fieldgroup.FieldGroup; +import com.vaadin.v7.ui.AbstractField; +import com.vaadin.v7.ui.ComboBox; +import com.vaadin.v7.ui.Field; + +import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.sample.GenoTypeResult; +import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.api.sample.PathogenTestResultType; +import de.symeda.sormas.api.sample.PathogenTestType; +import de.symeda.sormas.ui.utils.FieldHelper; + +/** + * Disease section for CRYPTOSPORIDIOSIS: same genotype result pattern as Measles. + * Fields live in the common layout; this section owns the visibility registrations. + */ +public class CryptosporidiosisDiseaseSectionLayout implements DiseaseSectionLayout { + + private static final List FIELD_IDS = + Collections.unmodifiableList(Arrays.asList(PathogenTestDto.GENOTYPE_RESULT, PathogenTestDto.GENOTYPE_RESULT_TEXT)); + + private Property.ValueChangeListener testTypeListener; + + @Override + public String getHtmlLayout() { + return ""; + } + + @Override + public Collection getFieldIds() { + return FIELD_IDS; + } + + @Override + public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease) { + Map> genoTypingDependencies = new HashMap<>(); + genoTypingDependencies.put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.MEASLES, Disease.CRYPTOSPORIDIOSIS)); + genoTypingDependencies.put(PathogenTestDto.TEST_TYPE, Arrays.asList(PathogenTestType.GENOTYPING)); + genoTypingDependencies.put(PathogenTestDto.TEST_RESULT, Arrays.asList(PathogenTestResultType.POSITIVE)); + FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.GENOTYPE_RESULT, genoTypingDependencies, true); + FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.GENOTYPE_RESULT_TEXT, PathogenTestDto.GENOTYPE_RESULT, GenoTypeResult.OTHER, true); + + testTypeListener = e -> { + Field genoTypingCB = fieldGroup.getField(PathogenTestDto.GENOTYPE_RESULT); + if (genoTypingCB instanceof ComboBox) { + Disease currentDisease = (Disease) fieldGroup.getField(PathogenTestDto.TESTED_DISEASE).getValue(); + FieldHelper.updateItems(currentDisease, (ComboBox) genoTypingCB, GenoTypeResult.class); + } + }; + Field testTypeField = fieldGroup.getField(PathogenTestDto.TEST_TYPE); + if (testTypeField != null) { + testTypeField.addValueChangeListener(testTypeListener); + } + } + + @Override + public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { + if (testTypeListener != null) { + Field testTypeField = fieldGroup.getField(PathogenTestDto.TEST_TYPE); + if (testTypeField != null) { + testTypeField.removeValueChangeListener(testTypeListener); + } + testTypeListener = null; + } + } + + @Override + public void onTestTypeChanged(PathogenTestType testType, Disease disease, AbstractField testResultField) { + // genotype item update is handled by the registered testTypeListener + } + + @Override + public Disease[] getDiseases() { + return new Disease[] { + Disease.CRYPTOSPORIDIOSIS }; + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/CsmDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/CsmDiseaseSectionLayout.java new file mode 100644 index 00000000000..7ef40440b89 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/CsmDiseaseSectionLayout.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2018 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * + * 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.samples; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.vaadin.ui.CustomLayout; +import com.vaadin.v7.data.fieldgroup.FieldGroup; + +import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.api.sample.PathogenTestResultType; +import de.symeda.sormas.ui.utils.FieldHelper; + +/** + * Disease section for CSM: wires serotype visibility (positive result only). + * Field lives in the common layout; this section owns the visibility registration. + */ +public class CsmDiseaseSectionLayout implements DiseaseSectionLayout { + + private static final List FIELD_IDS = Collections.singletonList(PathogenTestDto.SEROTYPE); + + @Override + public String getHtmlLayout() { + return ""; + } + + @Override + public Collection getFieldIds() { + return FIELD_IDS; + } + + @Override + public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease) { + Map> serotypeVisibilityDependencies = new HashMap<>(); + serotypeVisibilityDependencies.put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.CSM)); + serotypeVisibilityDependencies.put(PathogenTestDto.TEST_RESULT, Arrays.asList(PathogenTestResultType.POSITIVE)); + FieldHelper.setVisibleWhen(fieldGroup, Arrays.asList(PathogenTestDto.SEROTYPE), serotypeVisibilityDependencies, true); + } + + @Override + public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { + // setVisibleWhen listeners are attached to TESTED_DISEASE and TEST_RESULT source fields. + // Those source fields remain in the FieldGroup across section swaps and their + // multi-condition visibility logic is harmless when this disease is not active, + // because the TESTED_DISEASE condition will never match CSM for another section. + } + + @Override + public Disease[] getDiseases() { + return new Disease[] { + Disease.CSM }; + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/DiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/DiseaseSectionLayout.java index ce975438c47..a9d3123c82c 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/DiseaseSectionLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/DiseaseSectionLayout.java @@ -76,9 +76,27 @@ default void onTestTypeChanged(PathogenTestType testType, Disease disease, Abstr /** Factory: returns the correct section implementation for the given disease. */ static DiseaseSectionLayout forDisease(Disease disease) { - if (disease == Disease.TUBERCULOSIS || disease == Disease.LATENT_TUBERCULOSIS) { + if (disease == null) { + return new DefaultDiseaseSectionLayout(); + } + switch (disease) { + case TUBERCULOSIS: + case LATENT_TUBERCULOSIS: return new TuberculosisDiseaseSectionLayout(); + case MEASLES: + return new MeaslesDiseaseSectionLayout(); + case CRYPTOSPORIDIOSIS: + return new CryptosporidiosisDiseaseSectionLayout(); + case INVASIVE_MENINGOCOCCAL_INFECTION: + return new ImiDiseaseSectionLayout(); + case INVASIVE_PNEUMOCOCCAL_INFECTION: + return new IpiDiseaseSectionLayout(); + case CSM: + return new CsmDiseaseSectionLayout(); + case CORONAVIRUS: + return new CoronavirusDiseaseSectionLayout(); + default: + return new DefaultDiseaseSectionLayout(); } - return new DefaultDiseaseSectionLayout(); } } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/ImiDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/ImiDiseaseSectionLayout.java new file mode 100644 index 00000000000..6f8cbf9380f --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/ImiDiseaseSectionLayout.java @@ -0,0 +1,122 @@ +/******************************************************************************* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2018 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * + * 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.samples; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.vaadin.ui.CustomLayout; +import com.vaadin.v7.data.fieldgroup.FieldGroup; +import com.vaadin.v7.ui.AbstractField; + +import de.symeda.sormas.api.CountryHelper; +import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.FacadeProvider; +import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.api.sample.PathogenTestResultType; +import de.symeda.sormas.api.sample.PathogenTestType; +import de.symeda.sormas.api.sample.SeroGroupSpecification; +import de.symeda.sormas.api.utils.fieldaccess.UiFieldAccessCheckers; +import de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers; +import de.symeda.sormas.ui.therapy.DrugSusceptibilityForm; +import de.symeda.sormas.ui.utils.FieldHelper; + +/** + * Disease section for INVASIVE_MENINGOCOCCAL_INFECTION (IMI): + * wires seroGroupSpecification + seroGroupSpecificationText visibility, + * and owns the DrugSusceptibilityForm for IMI. + * Fields live in the common layout; this section owns the visibility registrations. + */ +public class ImiDiseaseSectionLayout implements DiseaseSectionLayout { + + private static final List FIELD_IDS = Collections.unmodifiableList( + Arrays.asList(PathogenTestDto.SERO_GROUP_SPECIFICATION, PathogenTestDto.SERO_GROUP_SPECIFICATION_TEXT, PathogenTestDto.DRUG_SUSCEPTIBILITY)); + + private DrugSusceptibilityForm drugSusceptibilityField; + + @Override + public String getHtmlLayout() { + return ""; + } + + @Override + public Collection getFieldIds() { + return FIELD_IDS; + } + + @Override + public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease) { + Map> imiSeroTypingDependencies = new HashMap<>(); + imiSeroTypingDependencies.put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.INVASIVE_MENINGOCOCCAL_INFECTION)); + imiSeroTypingDependencies.put(PathogenTestDto.TEST_RESULT, Arrays.asList(PathogenTestResultType.POSITIVE)); + imiSeroTypingDependencies.put( + PathogenTestDto.TEST_TYPE, + Arrays.asList( + PathogenTestType.SEROGROUPING, + PathogenTestType.MULTILOCUS_SEQUENCE_TYPING, + PathogenTestType.SLIDE_AGGLUTINATION, + PathogenTestType.WHOLE_GENOME_SEQUENCING)); + FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.SERO_GROUP_SPECIFICATION, imiSeroTypingDependencies, true); + FieldHelper.setVisibleWhen( + fieldGroup, + PathogenTestDto.SERO_GROUP_SPECIFICATION_TEXT, + PathogenTestDto.SERO_GROUP_SPECIFICATION, + SeroGroupSpecification.OTHER, + true); + + drugSusceptibilityField = new DrugSusceptibilityForm( + FieldVisibilityCheckers.getNoop(), + UiFieldAccessCheckers.getDefault(true, FacadeProvider.getConfigFacade().getCountryLocale())); + drugSusceptibilityField.setCaption(null); + fieldGroup.bind(drugSusceptibilityField, PathogenTestDto.DRUG_SUSCEPTIBILITY); + } + + @Override + public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { + if (drugSusceptibilityField != null) { + fieldGroup.unbind(drugSusceptibilityField); + panel.removeComponent(drugSusceptibilityField); + drugSusceptibilityField = null; + } + } + + @Override + public void onTestTypeChanged(PathogenTestType testType, Disease disease, AbstractField testResultField) { + if (drugSusceptibilityField != null) { + drugSusceptibilityField.updateFieldsVisibility(disease, testType); + } + + if (testType == null || !FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { + return; + } + + if (disease == Disease.INVASIVE_MENINGOCOCCAL_INFECTION && testType == PathogenTestType.ANTIBIOTIC_SUSCEPTIBILITY) { + testResultField.setValue(PathogenTestResultType.POSITIVE); + } + } + + @Override + public Disease[] getDiseases() { + return new Disease[] { + Disease.INVASIVE_MENINGOCOCCAL_INFECTION }; + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/IpiDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/IpiDiseaseSectionLayout.java new file mode 100644 index 00000000000..255e64da406 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/IpiDiseaseSectionLayout.java @@ -0,0 +1,136 @@ +/******************************************************************************* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2018 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * + * 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.samples; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.vaadin.ui.CustomLayout; +import com.vaadin.v7.data.fieldgroup.FieldGroup; +import com.vaadin.v7.ui.AbstractField; + +import de.symeda.sormas.api.CountryHelper; +import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.FacadeProvider; +import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.api.sample.PathogenTestResultType; +import de.symeda.sormas.api.sample.PathogenTestType; +import de.symeda.sormas.api.sample.SerotypingMethod; +import de.symeda.sormas.api.utils.fieldaccess.UiFieldAccessCheckers; +import de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers; +import de.symeda.sormas.ui.therapy.DrugSusceptibilityForm; +import de.symeda.sormas.ui.utils.FieldHelper; + +/** + * Disease section for INVASIVE_PNEUMOCOCCAL_INFECTION (IPI): + * wires serotype + serotypingMethod + serotypingMethodText visibility, + * and owns the DrugSusceptibilityForm for IPI. + * Fields live in the common layout; this section owns the visibility registrations. + */ +public class IpiDiseaseSectionLayout implements DiseaseSectionLayout { + + private static final List FIELD_IDS = Collections.unmodifiableList( + Arrays.asList( + PathogenTestDto.SEROTYPE, + PathogenTestDto.SEROTYPING_METHOD, + PathogenTestDto.SERO_TYPING_METHOD_TEXT, + PathogenTestDto.DRUG_SUSCEPTIBILITY)); + + private DrugSusceptibilityForm drugSusceptibilityField; + + @Override + public String getHtmlLayout() { + return ""; + } + + @Override + public Collection getFieldIds() { + return FIELD_IDS; + } + + @Override + public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease) { + // serotype + serotypingMethod visible on SEROGROUPING + POSITIVE + Map> serotypeAndMethodDependencies = new HashMap<>(); + serotypeAndMethodDependencies.put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.INVASIVE_PNEUMOCOCCAL_INFECTION)); + serotypeAndMethodDependencies.put(PathogenTestDto.TEST_TYPE, Arrays.asList(PathogenTestType.SEROGROUPING)); + serotypeAndMethodDependencies.put(PathogenTestDto.TEST_RESULT, Arrays.asList(PathogenTestResultType.POSITIVE)); + FieldHelper.setVisibleWhen( + fieldGroup, + Arrays.asList(PathogenTestDto.SEROTYPE, PathogenTestDto.SEROTYPING_METHOD), + serotypeAndMethodDependencies, + true); + + // serotype also visible on WGS/SLIDE_AGGLUTINATION/MLST/SEROGROUPING + POSITIVE + Map> serotypeExtendedDependencies = new HashMap<>(); + serotypeExtendedDependencies.put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.INVASIVE_PNEUMOCOCCAL_INFECTION)); + serotypeExtendedDependencies.put( + PathogenTestDto.TEST_TYPE, + Arrays.asList( + PathogenTestType.WHOLE_GENOME_SEQUENCING, + PathogenTestType.SLIDE_AGGLUTINATION, + PathogenTestType.MULTILOCUS_SEQUENCE_TYPING, + PathogenTestType.SEROGROUPING)); + serotypeExtendedDependencies.put(PathogenTestDto.TEST_RESULT, Arrays.asList(PathogenTestResultType.POSITIVE)); + FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.SEROTYPE, serotypeExtendedDependencies, true); + + // serotypingMethodText visible when method = OTHER + FieldHelper + .setVisibleWhen(fieldGroup, PathogenTestDto.SERO_TYPING_METHOD_TEXT, PathogenTestDto.SEROTYPING_METHOD, SerotypingMethod.OTHER, true); + + drugSusceptibilityField = new DrugSusceptibilityForm( + FieldVisibilityCheckers.getNoop(), + UiFieldAccessCheckers.getDefault(true, FacadeProvider.getConfigFacade().getCountryLocale())); + drugSusceptibilityField.setCaption(null); + fieldGroup.bind(drugSusceptibilityField, PathogenTestDto.DRUG_SUSCEPTIBILITY); + } + + @Override + public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { + if (drugSusceptibilityField != null) { + fieldGroup.unbind(drugSusceptibilityField); + panel.removeComponent(drugSusceptibilityField); + drugSusceptibilityField = null; + } + } + + @Override + public void onTestTypeChanged(PathogenTestType testType, Disease disease, AbstractField testResultField) { + if (drugSusceptibilityField != null) { + drugSusceptibilityField.updateFieldsVisibility(disease, testType); + } + + if (testType == null || !FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { + return; + } + + if (disease == Disease.INVASIVE_PNEUMOCOCCAL_INFECTION && testType == PathogenTestType.ANTIBIOTIC_SUSCEPTIBILITY) { + testResultField.setValue(PathogenTestResultType.POSITIVE); + } + } + + @Override + public Disease[] getDiseases() { + return new Disease[] { + Disease.INVASIVE_PNEUMOCOCCAL_INFECTION }; + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/MeaslesDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/MeaslesDiseaseSectionLayout.java new file mode 100644 index 00000000000..4fe509a349a --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/MeaslesDiseaseSectionLayout.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2018 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * + * 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.samples; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.vaadin.ui.CustomLayout; +import com.vaadin.v7.data.Property; +import com.vaadin.v7.data.fieldgroup.FieldGroup; +import com.vaadin.v7.ui.AbstractField; +import com.vaadin.v7.ui.ComboBox; +import com.vaadin.v7.ui.Field; + +import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.sample.GenoTypeResult; +import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.api.sample.PathogenTestResultType; +import de.symeda.sormas.api.sample.PathogenTestType; +import de.symeda.sormas.ui.utils.FieldHelper; + +/** + * Disease section for MEASLES: wires genotype result + genotype result text visibility. + * Fields live in the common layout; this section owns the visibility registrations. + */ +public class MeaslesDiseaseSectionLayout implements DiseaseSectionLayout { + + private static final List FIELD_IDS = + Collections.unmodifiableList(Arrays.asList(PathogenTestDto.GENOTYPE_RESULT, PathogenTestDto.GENOTYPE_RESULT_TEXT)); + + private Property.ValueChangeListener testTypeListener; + + @Override + public String getHtmlLayout() { + return ""; + } + + @Override + public Collection getFieldIds() { + return FIELD_IDS; + } + + @Override + public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease) { + Map> genoTypingDependencies = new HashMap<>(); + genoTypingDependencies.put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.MEASLES, Disease.CRYPTOSPORIDIOSIS)); + genoTypingDependencies.put(PathogenTestDto.TEST_TYPE, Arrays.asList(PathogenTestType.GENOTYPING)); + genoTypingDependencies.put(PathogenTestDto.TEST_RESULT, Arrays.asList(PathogenTestResultType.POSITIVE)); + FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.GENOTYPE_RESULT, genoTypingDependencies, true); + FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.GENOTYPE_RESULT_TEXT, PathogenTestDto.GENOTYPE_RESULT, GenoTypeResult.OTHER, true); + + // Update genotype items when test type changes + testTypeListener = e -> { + Field genoTypingCB = fieldGroup.getField(PathogenTestDto.GENOTYPE_RESULT); + if (genoTypingCB instanceof ComboBox) { + Disease currentDisease = (Disease) fieldGroup.getField(PathogenTestDto.TESTED_DISEASE).getValue(); + FieldHelper.updateItems(currentDisease, (ComboBox) genoTypingCB, GenoTypeResult.class); + } + }; + Field testTypeField = fieldGroup.getField(PathogenTestDto.TEST_TYPE); + if (testTypeField != null) { + testTypeField.addValueChangeListener(testTypeListener); + } + } + + @Override + public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { + if (testTypeListener != null) { + Field testTypeField = fieldGroup.getField(PathogenTestDto.TEST_TYPE); + if (testTypeField != null) { + testTypeField.removeValueChangeListener(testTypeListener); + } + testTypeListener = null; + } + } + + @Override + public void onTestTypeChanged(PathogenTestType testType, Disease disease, AbstractField testResultField) { + // genotype item update is handled by the registered testTypeListener + } + + @Override + public Disease[] getDiseases() { + return new Disease[] { + Disease.MEASLES }; + } +} From 5451cbeefcebda15078e9ebd2496098cd3f63d9b Mon Sep 17 00:00:00 2001 From: Harold Asiimwe Date: Tue, 3 Mar 2026 13:56:41 +0300 Subject: [PATCH 05/30] #13857 - PathogenTestForm clean-up --- .../sormas/ui/samples/PathogenTestForm.java | 488 ++---------------- .../TuberculosisDiseaseSectionLayout.java | 44 +- .../sormas/ui/utils/AbstractEditForm.java | 8 + 3 files changed, 97 insertions(+), 443 deletions(-) diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java index 434b2196f07..e75153b3498 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java @@ -60,17 +60,13 @@ import de.symeda.sormas.api.i18n.Validations; import de.symeda.sormas.api.infrastructure.facility.FacilityDto; import de.symeda.sormas.api.infrastructure.facility.FacilityReferenceDto; -import de.symeda.sormas.api.sample.GenoTypeResult; import de.symeda.sormas.api.sample.PathogenStrainCallStatus; import de.symeda.sormas.api.sample.PathogenTestDto; import de.symeda.sormas.api.sample.PathogenTestResultType; import de.symeda.sormas.api.sample.PathogenTestType; import de.symeda.sormas.api.sample.SampleDto; import de.symeda.sormas.api.sample.SamplePurpose; -import de.symeda.sormas.api.sample.SeroGroupSpecification; -import de.symeda.sormas.api.sample.SerotypingMethod; import de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers; -import de.symeda.sormas.ui.therapy.DrugSusceptibilityForm; import de.symeda.sormas.ui.utils.AbstractEditForm; import de.symeda.sormas.ui.utils.CssStyles; import de.symeda.sormas.ui.utils.DateComparisonValidator; @@ -107,9 +103,6 @@ public class PathogenTestForm extends AbstractEditForm { fluidRowLocs(6,PathogenTestDto.TEST_RESULT, 4, PathogenTestDto.TEST_RESULT_VERIFIED, 2,PathogenTestDto.PRELIMINARY) + fluidRowLocs(PathogenTestDto.TESTED_DISEASE_VARIANT, PathogenTestDto.TESTED_DISEASE_VARIANT_DETAILS) + loc(DISEASE_SECTION_LOC) + - fluidRowLocs(4,PathogenTestDto.SEROTYPE, 4,PathogenTestDto.SEROTYPING_METHOD, 4,PathogenTestDto.SERO_TYPING_METHOD_TEXT) + - fluidRowLocs(6,PathogenTestDto.SERO_GROUP_SPECIFICATION , 6, PathogenTestDto.SERO_GROUP_SPECIFICATION_TEXT) + - fluidRowLocs(4,PathogenTestDto.GENOTYPE_RESULT,6, PathogenTestDto.GENOTYPE_RESULT_TEXT) + fluidRowLocs(PathogenTestDto.FOUR_FOLD_INCREASE_ANTIBODY_TITER, "") + fluidRowLocs(PathogenTestDto.CQ_VALUE, "") + fluidRowLocs(PathogenTestDto.CT_VALUE_E, PathogenTestDto.CT_VALUE_N) + @@ -157,40 +150,6 @@ public class PathogenTestForm extends AbstractEditForm { } }); - public static final Map> RIFAMPICIN_RESISTANT_VISIBILITY_CONDITIONS = Collections.unmodifiableMap(new HashMap<>() { - - { - put(PathogenTestDto.TESTED_DISEASE, Collections.unmodifiableList(Arrays.asList(Disease.LATENT_TUBERCULOSIS, Disease.TUBERCULOSIS))); - put(PathogenTestDto.TEST_TYPE, Collections.unmodifiableList(Arrays.asList(PathogenTestType.PCR_RT_PCR))); - put(PathogenTestDto.TEST_RESULT, Collections.unmodifiableList(Arrays.asList(PathogenTestResultType.POSITIVE))); - } - }); - - public static final Map> TEST_SCALE_VISIBILITY_CONDITIONS = Collections.unmodifiableMap(new HashMap<>() { - - { - put(PathogenTestDto.TESTED_DISEASE, Collections.unmodifiableList(Arrays.asList(Disease.LATENT_TUBERCULOSIS, Disease.TUBERCULOSIS))); - put(PathogenTestDto.TEST_TYPE, Collections.unmodifiableList(Arrays.asList(PathogenTestType.MICROSCOPY))); - } - }); - - public static final Map> STRAIN_CALL_STATUS_VISIBILITY_CONDITIONS = Collections.unmodifiableMap(new HashMap<>() { - - { - put(PathogenTestDto.TESTED_DISEASE, Collections.unmodifiableList(Arrays.asList(Disease.LATENT_TUBERCULOSIS, Disease.TUBERCULOSIS))); - put(PathogenTestDto.TEST_TYPE, Collections.unmodifiableList(Arrays.asList(PathogenTestType.BEIJINGGENOTYPING))); - } - }); - - public static final Map> SPECIE_VISIBILITY_CONDITIONS = Collections.unmodifiableMap(new HashMap<>() { - - { - put(PathogenTestDto.TESTED_DISEASE, Collections.unmodifiableList(Arrays.asList(Disease.LATENT_TUBERCULOSIS, Disease.TUBERCULOSIS))); - put(PathogenTestDto.TEST_TYPE, Collections.unmodifiableList(Arrays.asList(PathogenTestType.SPOLIGOTYPING))); - put(PathogenTestDto.TEST_RESULT, Collections.unmodifiableList(Arrays.asList(PathogenTestResultType.POSITIVE))); - } - }); - public static final Map> PCR_TEST_SPECIFICATION_VISIBILITY_CONDITIONS = Collections.unmodifiableMap(new HashMap<>() { { @@ -210,16 +169,10 @@ public class PathogenTestForm extends AbstractEditForm { private ComboBox testTypeField; private ComboBox diseaseField; private ComboBox testResultField; - private DrugSusceptibilityForm drugSusceptibilityField; private TextField testTypeTextField; private ComboBox pcrTestSpecification; private Disease disease; private TextField typingIdField; - private ComboBox genoTypingCB; - private TextField genoTypingResultTextTF; - - private ComboBox seroGrpSepcCB; - private TextField seroGrpSpecTxt; // Disease section swap support private DiseaseSectionLayout activeSection = new DefaultDiseaseSectionLayout(); @@ -292,52 +245,6 @@ private static void setCqValueVisibility( } } - private void updateDrugSusceptibilityFieldSpecifications(PathogenTestType testType, Disease disease) { - - // Hide or show drug susceptibility fields based on the disease and test type (if disease is null then drug susceptibility should be hidden) - if (drugSusceptibilityField != null) { - drugSusceptibilityField.updateFieldsVisibility(disease, testType); - } - - // if the disease is null, means that we are dealing with a environment sample - // and we don't need to update the result field - if (disease == null) { - return; - } - - // if the test type is null we just clear the result field - if (testType == null) { - testResultField.setValue(null); - return; - } - - // FIXME: why was this here originally? - // TODO: move this to another place, should be in listeners for disease/testType. - - if ((FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG))) { - - // testResult=NOT_APPLICABLE for Tuberculosis diseases, test types BEIJINGGENOTYPING,MIRU_PATTERN_CODE,ANTIBIOTIC_SUSCEPTIBILITY - if ((disease == Disease.LATENT_TUBERCULOSIS || disease == Disease.TUBERCULOSIS) - && (testType == PathogenTestType.BEIJINGGENOTYPING - || testType == PathogenTestType.MIRU_PATTERN_CODE - || testType == PathogenTestType.ANTIBIOTIC_SUSCEPTIBILITY)) { - testResultField.setValue(PathogenTestResultType.NOT_APPLICABLE); - } - - // testResult=POSITIVE for Tuberculosis diseases, test type SPOLIGOTYPING - if ((disease == Disease.LATENT_TUBERCULOSIS || disease == Disease.TUBERCULOSIS) && (testType == PathogenTestType.SPOLIGOTYPING)) { - testResultField.setValue(PathogenTestResultType.POSITIVE); - } - - // testResult=POSITIVE for IMI and IPI, test type ANTIBIOTIC_SUSCEPTIBILITY - if ((disease == Disease.INVASIVE_MENINGOCOCCAL_INFECTION || disease == Disease.INVASIVE_PNEUMOCOCCAL_INFECTION) - && testType == PathogenTestType.ANTIBIOTIC_SUSCEPTIBILITY) { - testResultField.setValue(PathogenTestResultType.POSITIVE); - } - } - - } - private Date getSampleDate() { if (sample != null) { return sample.getSampleDateTime(); @@ -385,26 +292,6 @@ public void setValue(PathogenTestDto newFieldValue) throws ReadOnlyException, Co if (specieFieldDynamic != null) { specieFieldDynamic.setValue(newFieldValue.getSpecie()); } - if (!genoTypingCB.isReadOnly()) { - genoTypingCB.setValue(newFieldValue.getGenoTypeResult()); - - } - - if (!genoTypingResultTextTF.isReadOnly()) { - genoTypingResultTextTF.setValue(newFieldValue.getGenoTypeResultText()); - } - - if (!seroGrpSepcCB.isReadOnly()) { - seroGrpSepcCB.setValue(newFieldValue.getSeroGroupSpecification()); - } - - if (!seroGrpSpecTxt.isReadOnly()) { - seroGrpSpecTxt.setValue(newFieldValue.getSeroGroupSpecificationText()); - } - - if (drugSusceptibilityField != null) { - drugSusceptibilityField.forceUpdateDrugSusceptibilityFields(); - } markAsDirty(); } @@ -428,8 +315,6 @@ protected void addFields() { testTypeField = addField(PathogenTestDto.TEST_TYPE, ComboBox.class); testTypeField.setItemCaptionMode(ItemCaptionMode.ID_TOSTRING); testTypeField.setImmediate(true); - TextField seroTypingMethodText = addField(PathogenTestDto.SERO_TYPING_METHOD_TEXT); - seroTypingMethodText.setVisible(false); pcrTestSpecification = addField(PathogenTestDto.PCR_TEST_SPECIFICATION, ComboBox.class); testTypeTextField = addField(PathogenTestDto.TEST_TYPE_TEXT, TextField.class); FieldHelper.addSoftRequiredStyle(testTypeTextField); @@ -489,11 +374,6 @@ protected void addFields() { diseaseVariantField.setCaption(I18nProperties.getCaption(Captions.PathogenTest_rsv_testedDiseaseVariant)); diseaseVariantDetailsField.setCaption(I18nProperties.getCaption(Captions.PathogenTest_rsv_testedDiseaseVariantDetails)); } - genoTypingCB = addField(PathogenTestDto.GENOTYPE_RESULT, ComboBox.class); - genoTypingCB.setVisible(true); - genoTypingResultTextTF = addField(PathogenTestDto.GENOTYPE_RESULT_TEXT, TextField.class); - genoTypingResultTextTF.setVisible(true); - ComboBox testedPathogenField = addCustomizableEnumField(PathogenTestDto.TESTED_PATHOGEN); TextField testedPathogenDetailsField = addField(PathogenTestDto.TESTED_PATHOGEN_DETAILS, TextField.class); testedPathogenDetailsField.setVisible(false); @@ -529,21 +409,11 @@ protected void addFields() { if (!FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { testResultField.removeItem(PathogenTestResultType.NOT_APPLICABLE); } - TextField seroTypeTF = addField(PathogenTestDto.SEROTYPE, TextField.class); - // Bind the initial disease section (default = no-op; swapped via swapDiseaseSection() on disease change) activeSection = DiseaseSectionLayout.forDisease(disease); diseaseSectionPanel.setTemplateContents(activeSection.getHtmlLayout()); activeSection.bindFields(getFieldGroup(), diseaseSectionPanel, disease); - seroTypeTF.setVisible(false); - - ComboBox seroTypeMetCB = addField(PathogenTestDto.SEROTYPING_METHOD, ComboBox.class); - seroTypeMetCB.setVisible(false); - seroGrpSepcCB = addField(PathogenTestDto.SERO_GROUP_SPECIFICATION, ComboBox.class); - seroGrpSepcCB.setVisible(false); - seroGrpSpecTxt = addField(PathogenTestDto.SERO_GROUP_SPECIFICATION_TEXT, TextField.class); - TextField cqValueField = addField(FieldConfiguration.withConversionError(PathogenTestDto.CQ_VALUE, Validations.onlyNumbersAllowed)); if (!FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { cqValueField.setVisible(false); @@ -625,96 +495,6 @@ protected void addFields() { Arrays.asList(PathogenTestType.PCR_RT_PCR, PathogenTestType.DNA_MICROARRAY, PathogenTestType.SEQUENCING), true); - // Serotype field visibility specification for CSM disease - Map> serotypeVisibilityDependencies = new HashMap>() { - - private static final long serialVersionUID = 1967952323596082247L; - - { - put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.CSM)); - put(PathogenTestDto.TEST_RESULT, Arrays.asList(PathogenTestResultType.POSITIVE)); - } - }; - FieldHelper.setVisibleWhen(getFieldGroup(), Arrays.asList(PathogenTestDto.SEROTYPE), serotypeVisibilityDependencies, true); - // End of Serotype field visibility specification for CSM disease - - // IPI visibility check with a positive test result, show serotype and serotyping method fields - Map> ipiSeroTypeAndMethodVisibilityDependencies = new HashMap>() { - - private static final long serialVersionUID = 1967952323596082247L; - { - put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.INVASIVE_PNEUMOCOCCAL_INFECTION)); - put(PathogenTestDto.TEST_TYPE, Arrays.asList(PathogenTestType.SEROGROUPING)); - put(PathogenTestDto.TEST_RESULT, Arrays.asList(PathogenTestResultType.POSITIVE)); - } - }; - FieldHelper.setVisibleWhen( - getFieldGroup(), - Arrays.asList(PathogenTestDto.SEROTYPE, PathogenTestDto.SEROTYPING_METHOD), - ipiSeroTypeAndMethodVisibilityDependencies, - true); - Map> ipiSeroTypeVisibilityDependencies = new HashMap>() { - - private static final long serialVersionUID = 1967952323596082247L; - { - put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.INVASIVE_PNEUMOCOCCAL_INFECTION)); - put( - PathogenTestDto.TEST_TYPE, - Arrays.asList( - PathogenTestType.WHOLE_GENOME_SEQUENCING, - PathogenTestType.SLIDE_AGGLUTINATION, - PathogenTestType.MULTILOCUS_SEQUENCE_TYPING, - PathogenTestType.SEROGROUPING)); - put(PathogenTestDto.TEST_RESULT, Arrays.asList(PathogenTestResultType.POSITIVE)); - } - }; - FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.SEROTYPE, ipiSeroTypeVisibilityDependencies, true); - - FieldHelper.setVisibleWhen( - getFieldGroup(), - PathogenTestDto.SERO_TYPING_METHOD_TEXT, - PathogenTestDto.SEROTYPING_METHOD, - SerotypingMethod.OTHER, - true); - // End of IPI visibility check - - //IMI serogroup specification - Map> imiSeroTypingDependencies = new HashMap<>() { - - { - put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.INVASIVE_MENINGOCOCCAL_INFECTION)); - put(PathogenTestDto.TEST_RESULT, Arrays.asList(PathogenTestResultType.POSITIVE)); - put( - PathogenTestDto.TEST_TYPE, - Arrays.asList( - PathogenTestType.SEROGROUPING, - PathogenTestType.MULTILOCUS_SEQUENCE_TYPING, - PathogenTestType.SLIDE_AGGLUTINATION, - PathogenTestType.WHOLE_GENOME_SEQUENCING)); - } - }; - FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.SERO_GROUP_SPECIFICATION, imiSeroTypingDependencies, true); - FieldHelper.setVisibleWhen( - getFieldGroup(), - PathogenTestDto.SERO_GROUP_SPECIFICATION_TEXT, - PathogenTestDto.SERO_GROUP_SPECIFICATION, - SeroGroupSpecification.OTHER, - true); - // End of IMI serogroup specification - //Cryptosporidiosis for all countries Genotyping specification - Map> cryptoGenoTypingDependencies = new HashMap<>() { - - { - put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.MEASLES, Disease.CRYPTOSPORIDIOSIS)); - put(PathogenTestDto.TEST_TYPE, Arrays.asList(PathogenTestType.GENOTYPING)); - put(PathogenTestDto.TEST_RESULT, Arrays.asList(PathogenTestResultType.POSITIVE)); - } - }; - FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.GENOTYPE_RESULT, cryptoGenoTypingDependencies, true); - - FieldHelper - .setVisibleWhen(getFieldGroup(), PathogenTestDto.GENOTYPE_RESULT_TEXT, PathogenTestDto.GENOTYPE_RESULT, GenoTypeResult.OTHER, true); - //disease variant specifications for RSV and Influenza Map> diseaseVariantDependencies = new HashMap<>() { @@ -768,7 +548,10 @@ protected void addFields() { PathogenStrainCallStatus.class); } - updateDrugSusceptibilityFieldSpecifications((PathogenTestType) testTypeField.getValue(), disease); + activeSection.onTestTypeChanged( + (PathogenTestType) testTypeField.getValue(), + disease, + (com.vaadin.v7.ui.AbstractField) (Object) testResultField); } }); diseaseVariantField.addValueChangeListener(e -> { @@ -813,35 +596,34 @@ protected void addFields() { PathogenTestDto.CT_VALUE_ORF_1, PathogenTestDto.CT_VALUE_RDRP_S); } - // Show tube IGRA fields only for IGRA tests and Luxembourg - setVisibleClear( - PathogenTestType.IGRA == testType && FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG), - PathogenTestDto.TUBE_NIL, - PathogenTestDto.TUBE_NIL_GT10, - PathogenTestDto.TUBE_AG_TB1, - PathogenTestDto.TUBE_AG_TB1_GT10, - PathogenTestDto.TUBE_AG_TB2, - PathogenTestDto.TUBE_AG_TB2_GT10, - PathogenTestDto.TUBE_MITOGENE, - PathogenTestDto.TUBE_MITOGENE_GT10); - FieldHelper.updateItems((Disease) diseaseField.getValue(), genoTypingCB, GenoTypeResult.class); + // Show tube IGRA fields only for IGRA tests, Luxembourg, and TB section + if (activeSection instanceof TuberculosisDiseaseSectionLayout) { + setVisibleClear( + PathogenTestType.IGRA == testType + && FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG), + PathogenTestDto.TUBE_NIL, + PathogenTestDto.TUBE_NIL_GT10, + PathogenTestDto.TUBE_AG_TB1, + PathogenTestDto.TUBE_AG_TB1_GT10, + PathogenTestDto.TUBE_AG_TB2, + PathogenTestDto.TUBE_AG_TB2_GT10, + PathogenTestDto.TUBE_MITOGENE, + PathogenTestDto.TUBE_MITOGENE_GT10); + } } else { - setVisibleClear( - testTypeField.getValue() != null, - PathogenTestDto.SEROTYPE, - PathogenTestDto.SEROTYPING_METHOD, - PathogenTestDto.SERO_GROUP_SPECIFICATION); - // hide tube fields when no test type selected - setVisibleClear( - false, - PathogenTestDto.TUBE_NIL, - PathogenTestDto.TUBE_NIL_GT10, - PathogenTestDto.TUBE_AG_TB1, - PathogenTestDto.TUBE_AG_TB1_GT10, - PathogenTestDto.TUBE_AG_TB2, - PathogenTestDto.TUBE_AG_TB2_GT10, - PathogenTestDto.TUBE_MITOGENE, - PathogenTestDto.TUBE_MITOGENE_GT10); + // hide tube fields when no test type selected (TB section only) + if (activeSection instanceof TuberculosisDiseaseSectionLayout) { + setVisibleClear( + false, + PathogenTestDto.TUBE_NIL, + PathogenTestDto.TUBE_NIL_GT10, + PathogenTestDto.TUBE_AG_TB1, + PathogenTestDto.TUBE_AG_TB1_GT10, + PathogenTestDto.TUBE_AG_TB2, + PathogenTestDto.TUBE_AG_TB2_GT10, + PathogenTestDto.TUBE_MITOGENE, + PathogenTestDto.TUBE_MITOGENE_GT10); + } testResultField.clear(); testResultField.setEnabled(true); } @@ -852,7 +634,10 @@ protected void addFields() { testResultField.clear(); } - updateDrugSusceptibilityFieldSpecifications(testType, (Disease) diseaseField.getValue()); + activeSection.onTestTypeChanged( + testType, + (Disease) diseaseField.getValue(), + (com.vaadin.v7.ui.AbstractField) (Object) testResultField); }); lab.addValueChangeListener(event -> @@ -899,15 +684,6 @@ protected void addFields() { || isVisibleAllowed(PathogenTestDto.PRESCRIBER_COUNTRY)); } - static class TestTypeValueChangeListener implements ValueChangeListener { - - @Override - public void valueChange(com.vaadin.v7.data.Property.ValueChangeEvent event) { - // TODO Auto-generated method stub - - } - } - // ----------------------------------------------------------------------- // Disease section swap support // ----------------------------------------------------------------------- @@ -919,195 +695,31 @@ private void swapDiseaseSection(Disease newDisease) { return; // same section type, nothing to swap } + removeFromAllowedLists(activeSection.getFieldIds()); activeSection.unbindFields(getFieldGroup(), diseaseSectionPanel); activeSection = newSection; diseaseSectionPanel.setTemplateContents(newSection.getHtmlLayout()); newSection.bindFields(getFieldGroup(), diseaseSectionPanel, newDisease); + rebuildSectionFieldAllowances(newSection.getFieldIds()); } - /** Package-private: adds a disease-section field to the nested diseaseSectionPanel layout. */ - F addSectionField(String propertyId, Class fieldType) { - F field = getFieldGroup().buildAndBind(propertyId, (Object) propertyId, fieldType); - formatField(field, propertyId); - field.setId(propertyId); - diseaseSectionPanel.addComponent(field, propertyId); - return field; - } - - /** Package-private: adds the DrugSusceptibilityForm to the disease section panel. */ - void addSectionDrugSusceptibilityField() { - drugSusceptibilityField = (DrugSusceptibilityForm) addField( - diseaseSectionPanel, - PathogenTestDto.DRUG_SUSCEPTIBILITY, - new DrugSusceptibilityForm( - de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers.getNoop(), - de.symeda.sormas.api.utils.fieldaccess.UiFieldAccessCheckers.getDefault(true, FacadeProvider.getConfigFacade().getCountryLocale()))); - drugSusceptibilityField.setCaption(null); - addToVisibleAllowedFields(drugSusceptibilityField); - } - - /** Package-private: adds all tube IGRA fields to the disease section panel. */ - void addSectionTubeFields() { - addFields( - diseaseSectionPanel, - FieldConfiguration.builder(PathogenTestDto.TUBE_NIL) - .validationMessageProperty(de.symeda.sormas.api.i18n.Validations.onlyNumbersAllowed) - .valueChangeListener(e -> handleTubeNilChange((String) e.getProperty().getValue())) - .build(), - FieldConfiguration.builder(PathogenTestDto.TUBE_AG_TB1) - .validationMessageProperty(de.symeda.sormas.api.i18n.Validations.onlyNumbersAllowed) - .valueChangeListener(e -> handleTubeAgTb1Change((String) e.getProperty().getValue())) - .build(), - FieldConfiguration.builder(PathogenTestDto.TUBE_AG_TB2) - .validationMessageProperty(de.symeda.sormas.api.i18n.Validations.onlyNumbersAllowed) - .valueChangeListener(e -> handleTubeAgTb2Change((String) e.getProperty().getValue())) - .build(), - FieldConfiguration.builder(PathogenTestDto.TUBE_MITOGENE) - .validationMessageProperty(de.symeda.sormas.api.i18n.Validations.onlyNumbersAllowed) - .valueChangeListener(e -> handleTubeMitogeneChange((String) e.getProperty().getValue())) - .build()); - addFields( - diseaseSectionPanel, - FieldConfiguration.builder(PathogenTestDto.TUBE_NIL_GT10).valueChangeListener(e -> handleTubeNilGt10Change(e)).build(), - FieldConfiguration.builder(PathogenTestDto.TUBE_AG_TB1_GT10).valueChangeListener(e -> handleTubeAgTb1Gt10Change(e)).build(), - FieldConfiguration.builder(PathogenTestDto.TUBE_AG_TB2_GT10).valueChangeListener(e -> handleTubeAgTb2Gt10Change(e)).build(), - FieldConfiguration.builder(PathogenTestDto.TUBE_MITOGENE_GT10).valueChangeListener(e -> handleTubeMitogeneGt10Change(e)).build()); - - setVisibleClear( - false, - PathogenTestDto.TUBE_NIL, - PathogenTestDto.TUBE_NIL_GT10, - PathogenTestDto.TUBE_AG_TB1, - PathogenTestDto.TUBE_AG_TB1_GT10, - PathogenTestDto.TUBE_AG_TB2, - PathogenTestDto.TUBE_AG_TB2_GT10, - PathogenTestDto.TUBE_MITOGENE, - PathogenTestDto.TUBE_MITOGENE_GT10); - } - - /** Package-private: removes a field from both the disease section panel and the field group. */ - void removeSectionField(String propertyId) { - com.vaadin.v7.ui.Field field = getField(propertyId); - if (field != null) { - // Unbind first so listeners on sibling fields don't NPE when they fire during clear() - getFieldGroup().unbind(field); - diseaseSectionPanel.removeComponent(field); - } - } - - /** Returns the currently selected disease — used by section implementations. */ - Disease getCurrentDisease() { - return disease; - } - - // Tube field helpers — delegates from listeners in the original addFields() block - private void handleTubeNilChange(String val) { - NullableOptionGroup gt10 = getField(PathogenTestDto.TUBE_NIL_GT10); - if (gt10 == null) - return; - if (val == null) { - gt10.select(false); - return; - } - try { - gt10.select(Float.parseFloat(val) > 10); - } catch (NumberFormatException e) { - getField(PathogenTestDto.TUBE_NIL).clear(); - gt10.select(false); - } - } - - private void handleTubeAgTb1Change(String val) { - NullableOptionGroup gt10 = getField(PathogenTestDto.TUBE_AG_TB1_GT10); - if (gt10 == null) - return; - if (val == null) { - gt10.select(false); - return; - } - try { - gt10.select(Float.parseFloat(val) > 10); - } catch (NumberFormatException e) { - getField(PathogenTestDto.TUBE_AG_TB1).clear(); - gt10.select(false); - } - } - - private void handleTubeAgTb2Change(String val) { - NullableOptionGroup gt10 = getField(PathogenTestDto.TUBE_AG_TB2_GT10); - if (gt10 == null) - return; - if (val == null) { - gt10.select(false); - return; - } - try { - gt10.select(Float.parseFloat(val) > 10); - } catch (NumberFormatException e) { - getField(PathogenTestDto.TUBE_AG_TB2).clear(); - gt10.select(false); + private void rebuildSectionFieldAllowances(Collection fieldIds) { + for (String id : fieldIds) { + com.vaadin.v7.ui.Field f = getField(id); + if (f != null) { + addToVisibleAllowedFields(f); + } } } - private void handleTubeMitogeneChange(String val) { - NullableOptionGroup gt10 = getField(PathogenTestDto.TUBE_MITOGENE_GT10); - if (gt10 == null) - return; - if (val == null) { - gt10.select(false); - return; - } - try { - gt10.select(Float.parseFloat(val) > 10); - } catch (NumberFormatException e) { - getField(PathogenTestDto.TUBE_MITOGENE).clear(); - gt10.select(false); + private void removeFromAllowedLists(Collection fieldIds) { + for (String id : fieldIds) { + com.vaadin.v7.ui.Field f = getField(id); + if (f != null) { + removeFromVisibleAllowedFields(f); + removeFromEditableAllowedFields(f); + } } } - private void handleTubeNilGt10Change(com.vaadin.v7.data.Property.ValueChangeEvent e) { - Object v = e.getProperty().getValue() instanceof Collection - ? ((Collection) e.getProperty().getValue()).stream().findFirst().orElse(null) - : e.getProperty().getValue(); - handleGt10CheckboxChange(v, getField(PathogenTestDto.TUBE_NIL)); - } - - private void handleTubeAgTb1Gt10Change(com.vaadin.v7.data.Property.ValueChangeEvent e) { - Object v = e.getProperty().getValue() instanceof Collection - ? ((Collection) e.getProperty().getValue()).stream().findFirst().orElse(null) - : e.getProperty().getValue(); - handleGt10CheckboxChange(v, getField(PathogenTestDto.TUBE_AG_TB1)); - } - - private void handleTubeAgTb2Gt10Change(com.vaadin.v7.data.Property.ValueChangeEvent e) { - Object v = e.getProperty().getValue() instanceof Collection - ? ((Collection) e.getProperty().getValue()).stream().findFirst().orElse(null) - : e.getProperty().getValue(); - handleGt10CheckboxChange(v, getField(PathogenTestDto.TUBE_AG_TB2)); - } - - private void handleTubeMitogeneGt10Change(com.vaadin.v7.data.Property.ValueChangeEvent e) { - Object v = e.getProperty().getValue() instanceof Collection - ? ((Collection) e.getProperty().getValue()).stream().findFirst().orElse(null) - : e.getProperty().getValue(); - handleGt10CheckboxChange(v, getField(PathogenTestDto.TUBE_MITOGENE)); - } - - private void handleGt10CheckboxChange(Object singleValue, com.vaadin.v7.ui.Field numericField) { - if (numericField == null) - return; - String numVal = (String) numericField.getValue(); - if (singleValue == null || numVal == null) - return; - boolean checked = Boolean.TRUE.equals(singleValue); - try { - float f = Float.valueOf(numVal); - if (checked && f <= 10) - numericField.clear(); - else if (!checked && f > 10) - numericField.clear(); - } catch (NumberFormatException ex) { - numericField.clear(); - } - } } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TuberculosisDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TuberculosisDiseaseSectionLayout.java index d2826c30d2c..bae87cf7e1d 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TuberculosisDiseaseSectionLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TuberculosisDiseaseSectionLayout.java @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -84,6 +85,40 @@ public class TuberculosisDiseaseSectionLayout implements DiseaseSectionLayout { PathogenTestDto.TUBE_MITOGENE, PathogenTestDto.TUBE_MITOGENE_GT10); + public static final Map> RIFAMPICIN_RESISTANT_VISIBILITY_CONDITIONS = Collections.unmodifiableMap(new HashMap<>() { + + { + put(PathogenTestDto.TESTED_DISEASE, Collections.unmodifiableList(Arrays.asList(Disease.LATENT_TUBERCULOSIS, Disease.TUBERCULOSIS))); + put(PathogenTestDto.TEST_TYPE, Collections.unmodifiableList(Arrays.asList(PathogenTestType.PCR_RT_PCR))); + put(PathogenTestDto.TEST_RESULT, Collections.unmodifiableList(Arrays.asList(PathogenTestResultType.POSITIVE))); + } + }); + + public static final Map> TEST_SCALE_VISIBILITY_CONDITIONS = Collections.unmodifiableMap(new HashMap<>() { + + { + put(PathogenTestDto.TESTED_DISEASE, Collections.unmodifiableList(Arrays.asList(Disease.LATENT_TUBERCULOSIS, Disease.TUBERCULOSIS))); + put(PathogenTestDto.TEST_TYPE, Collections.unmodifiableList(Arrays.asList(PathogenTestType.MICROSCOPY))); + } + }); + + public static final Map> STRAIN_CALL_STATUS_VISIBILITY_CONDITIONS = Collections.unmodifiableMap(new HashMap<>() { + + { + put(PathogenTestDto.TESTED_DISEASE, Collections.unmodifiableList(Arrays.asList(Disease.LATENT_TUBERCULOSIS, Disease.TUBERCULOSIS))); + put(PathogenTestDto.TEST_TYPE, Collections.unmodifiableList(Arrays.asList(PathogenTestType.BEIJINGGENOTYPING))); + } + }); + + public static final Map> SPECIE_VISIBILITY_CONDITIONS = Collections.unmodifiableMap(new HashMap<>() { + + { + put(PathogenTestDto.TESTED_DISEASE, Collections.unmodifiableList(Arrays.asList(Disease.LATENT_TUBERCULOSIS, Disease.TUBERCULOSIS))); + put(PathogenTestDto.TEST_TYPE, Collections.unmodifiableList(Arrays.asList(PathogenTestType.SPOLIGOTYPING))); + put(PathogenTestDto.TEST_RESULT, Collections.unmodifiableList(Arrays.asList(PathogenTestResultType.POSITIVE))); + } + }); + // Held for onTestTypeChanged and unbindFields private DrugSusceptibilityForm drugSusceptibilityField; private List tubeFieldIds; @@ -189,11 +224,10 @@ private void bindLuxembourgVisibility(FieldGroup fieldGroup, ComboBox strainCall fieldGroup.getField(PathogenTestDto.TESTED_DISEASE).addValueChangeListener(diseaseListener); // Wire standard multi-source visibility conditions - FieldHelper - .setVisibleWhen(fieldGroup, PathogenTestDto.RIFAMPICIN_RESISTANT, PathogenTestForm.RIFAMPICIN_RESISTANT_VISIBILITY_CONDITIONS, true); - FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.TEST_SCALE, PathogenTestForm.TEST_SCALE_VISIBILITY_CONDITIONS, true); - FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.STRAIN_CALL_STATUS, PathogenTestForm.STRAIN_CALL_STATUS_VISIBILITY_CONDITIONS, true); - FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.SPECIE, PathogenTestForm.SPECIE_VISIBILITY_CONDITIONS, true); + FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.RIFAMPICIN_RESISTANT, RIFAMPICIN_RESISTANT_VISIBILITY_CONDITIONS, true); + FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.TEST_SCALE, TEST_SCALE_VISIBILITY_CONDITIONS, true); + FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.STRAIN_CALL_STATUS, STRAIN_CALL_STATUS_VISIBILITY_CONDITIONS, true); + FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.SPECIE, SPECIE_VISIBILITY_CONDITIONS, true); Map> miruCode = new HashMap<>(); miruCode.put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.TUBERCULOSIS, Disease.LATENT_TUBERCULOSIS)); diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/AbstractEditForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/AbstractEditForm.java index b5bc57d0af2..01f31da6b48 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/AbstractEditForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/AbstractEditForm.java @@ -568,6 +568,14 @@ protected void addToVisibleAllowedFields(Field field) { visibleAllowedFields.add(field); } + protected void removeFromVisibleAllowedFields(Field field) { + visibleAllowedFields.remove(field); + } + + protected void removeFromEditableAllowedFields(Field field) { + editableAllowedFields.remove(field); + } + /** * Sets the initial enabled states based on annotations and builds a list of all fields in a form * that are allowed to be enabled based on access rights From 0fbea3e98d1d3cad060bc9fde356f7fb8c0f4ada Mon Sep 17 00:00:00 2001 From: Harold Asiimwe Date: Tue, 3 Mar 2026 14:20:03 +0300 Subject: [PATCH 06/30] #13858 - Extracts Tube field logic --- .../sormas/ui/samples/TubeFieldHandler.java | 139 ++++++++++++++++++ .../TuberculosisDiseaseSectionLayout.java | 20 +-- 2 files changed, 147 insertions(+), 12 deletions(-) create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TubeFieldHandler.java diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TubeFieldHandler.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TubeFieldHandler.java new file mode 100644 index 00000000000..f50226513b9 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TubeFieldHandler.java @@ -0,0 +1,139 @@ +/******************************************************************************* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2018 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * + * 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.samples; + +import java.util.Collection; + +import com.vaadin.ui.CustomLayout; +import com.vaadin.v7.data.Property; +import com.vaadin.v7.data.fieldgroup.FieldGroup; +import com.vaadin.v7.ui.Field; +import com.vaadin.v7.ui.TextField; + +import de.symeda.sormas.api.i18n.I18nProperties; +import de.symeda.sormas.api.i18n.Validations; +import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.ui.utils.NullableOptionGroup; + +/** + * Standalone helper that manages the 4 IGRA tube field pairs for TB sections. + * Each pair consists of a numeric {@link TextField} and a GT10 {@link NullableOptionGroup} checkbox. + * The two fields are kept in sync: the numeric value auto-checks GT10 when > 10, + * and the checkbox clears the numeric value if they contradict. + */ +public class TubeFieldHandler { + + private final FieldGroup fieldGroup; + private final CustomLayout panel; + + public TubeFieldHandler(FieldGroup fieldGroup, CustomLayout panel) { + this.fieldGroup = fieldGroup; + this.panel = panel; + } + + /** Builds and binds all 4 tube pairs and wires their mutual listeners. */ + public void attachTubeFields() { + addTubePair(PathogenTestDto.TUBE_NIL, PathogenTestDto.TUBE_NIL_GT10); + addTubePair(PathogenTestDto.TUBE_AG_TB1, PathogenTestDto.TUBE_AG_TB1_GT10); + addTubePair(PathogenTestDto.TUBE_AG_TB2, PathogenTestDto.TUBE_AG_TB2_GT10); + addTubePair(PathogenTestDto.TUBE_MITOGENE, PathogenTestDto.TUBE_MITOGENE_GT10); + } + + /** Removes all 4 tube pairs from the panel and unbinds them from the field group. */ + public void detachTubeFields() { + detachTubePair(PathogenTestDto.TUBE_NIL, PathogenTestDto.TUBE_NIL_GT10); + detachTubePair(PathogenTestDto.TUBE_AG_TB1, PathogenTestDto.TUBE_AG_TB1_GT10); + detachTubePair(PathogenTestDto.TUBE_AG_TB2, PathogenTestDto.TUBE_AG_TB2_GT10); + detachTubePair(PathogenTestDto.TUBE_MITOGENE, PathogenTestDto.TUBE_MITOGENE_GT10); + } + + private void addTubePair(String numericId, String gt10Id) { + TextField numericField = fieldGroup.buildAndBind(numericId, (Object) numericId, TextField.class); + numericField.setId(numericId); + numericField.setConversionError(I18nProperties.getValidationError(Validations.onlyNumbersAllowed, numericField.getCaption())); + numericField.setVisible(false); + panel.addComponent(numericField, numericId); + + NullableOptionGroup gt10Field = fieldGroup.buildAndBind(gt10Id, (Object) gt10Id, NullableOptionGroup.class); + gt10Field.setId(gt10Id); + gt10Field.setVisible(false); + panel.addComponent(gt10Field, gt10Id); + + // numeric → auto-check GT10 when value > 10 + numericField.addValueChangeListener(e -> handleNumericChange((String) e.getProperty().getValue(), numericId, gt10Id)); + + // GT10 checkbox → clear numeric if value contradicts the checkbox + gt10Field.addValueChangeListener(e -> handleGt10Change(e, numericId)); + } + + private void detachTubePair(String numericId, String gt10Id) { + Field numericField = fieldGroup.getField(numericId); + if (numericField != null) { + fieldGroup.unbind(numericField); + panel.removeComponent(numericField); + } + Field gt10Field = fieldGroup.getField(gt10Id); + if (gt10Field != null) { + fieldGroup.unbind(gt10Field); + panel.removeComponent(gt10Field); + } + } + + private void handleNumericChange(String val, String numericId, String gt10Id) { + NullableOptionGroup gt10 = (NullableOptionGroup) fieldGroup.getField(gt10Id); + if (gt10 == null) { + return; + } + if (val == null) { + gt10.select(false); + return; + } + try { + gt10.select(Float.parseFloat(val) > 10); + } catch (NumberFormatException e) { + fieldGroup.getField(numericId).clear(); + gt10.select(false); + } + } + + private void handleGt10Change(Property.ValueChangeEvent e, String numericId) { + Object raw = e.getProperty().getValue() instanceof Collection + ? ((Collection) e.getProperty().getValue()).stream().findFirst().orElse(null) + : e.getProperty().getValue(); + + Field numericField = fieldGroup.getField(numericId); + if (numericField == null) { + return; + } + String numVal = (String) numericField.getValue(); + if (raw == null || numVal == null) { + return; + } + boolean checked = Boolean.TRUE.equals(raw); + try { + float f = Float.parseFloat(numVal); + if (checked && f <= 10) { + numericField.clear(); + } else if (!checked && f > 10) { + numericField.clear(); + } + } catch (NumberFormatException ex) { + numericField.clear(); + } + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TuberculosisDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TuberculosisDiseaseSectionLayout.java index bae87cf7e1d..5d662b71ea0 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TuberculosisDiseaseSectionLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TuberculosisDiseaseSectionLayout.java @@ -121,6 +121,7 @@ public class TuberculosisDiseaseSectionLayout implements DiseaseSectionLayout { // Held for onTestTypeChanged and unbindFields private DrugSusceptibilityForm drugSusceptibilityField; + private TubeFieldHandler tubeFieldHandler; private List tubeFieldIds; // Listeners registered on shared FieldGroup source fields — removed in unbindFields @@ -166,7 +167,6 @@ public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease diseas fieldGroup.bind(drugSusceptibilityField, PathogenTestDto.DRUG_SUSCEPTIBILITY); panel.addComponent(drugSusceptibilityField, PathogenTestDto.DRUG_SUSCEPTIBILITY); - // Tube fields — stub: built and added hidden; TubeFieldHandler wired in Phase 6 tubeFieldIds = Arrays.asList( PathogenTestDto.TUBE_NIL, PathogenTestDto.TUBE_NIL_GT10, @@ -177,17 +177,8 @@ public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease diseas PathogenTestDto.TUBE_MITOGENE, PathogenTestDto.TUBE_MITOGENE_GT10); - for (String tubeId : Arrays - .asList(PathogenTestDto.TUBE_NIL, PathogenTestDto.TUBE_AG_TB1, PathogenTestDto.TUBE_AG_TB2, PathogenTestDto.TUBE_MITOGENE)) { - buildAndAdd(fieldGroup, panel, tubeId, TextField.class).setVisible(false); - } - for (String gt10Id : Arrays.asList( - PathogenTestDto.TUBE_NIL_GT10, - PathogenTestDto.TUBE_AG_TB1_GT10, - PathogenTestDto.TUBE_AG_TB2_GT10, - PathogenTestDto.TUBE_MITOGENE_GT10)) { - buildAndAdd(fieldGroup, panel, gt10Id, NullableOptionGroup.class).setVisible(false); - } + tubeFieldHandler = new TubeFieldHandler(fieldGroup, panel); + tubeFieldHandler.attachTubeFields(); if (FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { bindLuxembourgVisibility(fieldGroup, strainCallStatus, disease); @@ -284,6 +275,11 @@ public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { diseaseListener = null; } + if (tubeFieldHandler != null) { + tubeFieldHandler.detachTubeFields(); + tubeFieldHandler = null; + } + for (String id : FIELD_IDS) { Field field = fieldGroup.getField(id); if (field != null) { From f440fd3846cbbf37c592ef6cb7e0694206dab7e4 Mon Sep 17 00:00:00 2001 From: Harold Asiimwe Date: Tue, 3 Mar 2026 14:21:42 +0300 Subject: [PATCH 07/30] PathogenTestForm - Removed PCR_TEST_SPECIFICATION loc from HTML_LAYOUT - Removed pcrTestSpecification instance variable - Removed addField(PCR_TEST_SPECIFICATION) from addFields() - Removed pcrTestSpecification.setValue(...) from setValue() - Removed pcrTestSpecification.setVisible(false) and FieldHelper.setVisibleWhen(PCR_TEST_SPECIFICATION) from addFields() CoronavirusDiseaseSectionLayout - Added HTML layout with fluidRowLocs(PCR_TEST_SPECIFICATION, "") - bindFields() now buildAndBinds the field, adds it to the panel, and wires visibility - unbindFields() now unbinds and removes the field from the panel - Updated class comment --- .../CoronavirusDiseaseSectionLayout.java | 34 ++++++++++++++----- .../sormas/ui/samples/PathogenTestForm.java | 7 ---- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/CoronavirusDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/CoronavirusDiseaseSectionLayout.java index 6a4f9caa078..821e770d305 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/CoronavirusDiseaseSectionLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/CoronavirusDiseaseSectionLayout.java @@ -17,27 +17,34 @@ *******************************************************************************/ package de.symeda.sormas.ui.samples; +import static de.symeda.sormas.ui.utils.LayoutUtil.fluidRowLocs; + import java.util.Collection; import java.util.Collections; import com.vaadin.ui.CustomLayout; import com.vaadin.v7.data.fieldgroup.FieldGroup; +import com.vaadin.v7.ui.ComboBox; +import com.vaadin.v7.ui.Field; import de.symeda.sormas.api.Disease; import de.symeda.sormas.api.sample.PathogenTestDto; import de.symeda.sormas.ui.utils.FieldHelper; /** - * Disease section for CORONAVIRUS: wires PCR_TEST_SPECIFICATION visibility. - * Field lives in the common layout; this section owns the visibility registration. + * Disease section for CORONAVIRUS: owns PCR_TEST_SPECIFICATION field and wires its visibility. */ public class CoronavirusDiseaseSectionLayout implements DiseaseSectionLayout { + //@formatter:off + private static final String HTML = fluidRowLocs(PathogenTestDto.PCR_TEST_SPECIFICATION, ""); + //@formatter:on + private static final Collection FIELD_IDS = Collections.singletonList(PathogenTestDto.PCR_TEST_SPECIFICATION); @Override public String getHtmlLayout() { - return ""; + return HTML; } @Override @@ -47,15 +54,24 @@ public Collection getFieldIds() { @Override public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease) { - FieldHelper - .setVisibleWhen(fieldGroup, PathogenTestDto.PCR_TEST_SPECIFICATION, PathogenTestForm.PCR_TEST_SPECIFICATION_VISIBILITY_CONDITIONS, true); + ComboBox pcrTestSpecification = fieldGroup.buildAndBind( + PathogenTestDto.PCR_TEST_SPECIFICATION, + (Object) PathogenTestDto.PCR_TEST_SPECIFICATION, + ComboBox.class); + pcrTestSpecification.setId(PathogenTestDto.PCR_TEST_SPECIFICATION); + pcrTestSpecification.setVisible(false); + panel.addComponent(pcrTestSpecification, PathogenTestDto.PCR_TEST_SPECIFICATION); + + FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.PCR_TEST_SPECIFICATION, PathogenTestForm.PCR_TEST_SPECIFICATION_VISIBILITY_CONDITIONS, true); } @Override public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { - // The PCR_TEST_SPECIFICATION visibility condition is keyed on TESTED_DISEASE=CORONAVIRUS, - // so it is inert when any other disease is active. No listener removal needed here; - // Phase 5 will consolidate all common-layout registrations. + Field field = fieldGroup.getField(PathogenTestDto.PCR_TEST_SPECIFICATION); + if (field != null) { + fieldGroup.unbind(field); + panel.removeComponent(field); + } } @Override @@ -63,4 +79,4 @@ public Disease[] getDiseases() { return new Disease[] { Disease.CORONAVIRUS }; } -} +} \ No newline at end of file diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java index e75153b3498..87207e7d93a 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java @@ -95,7 +95,6 @@ public class PathogenTestForm extends AbstractEditForm { fluidRowLocs(PathogenTestDto.EXTERNAL_ID, PathogenTestDto.EXTERNAL_ORDER_ID) + fluidRowLocs(PathogenTestDto.TESTED_DISEASE, PathogenTestDto.TESTED_DISEASE_DETAILS) + fluidRowLocs(PathogenTestDto.TEST_TYPE, PathogenTestDto.TEST_TYPE_TEXT) + - fluidRowLocs(PathogenTestDto.PCR_TEST_SPECIFICATION, "") + fluidRowLocs(PathogenTestDto.TESTED_PATHOGEN, PathogenTestDto.TESTED_PATHOGEN_DETAILS) + fluidRowLocs(PathogenTestDto.TYPING_ID, "") + fluidRowLocs(PathogenTestDto.TEST_DATE_TIME, PathogenTestDto.LAB) + @@ -170,7 +169,6 @@ public class PathogenTestForm extends AbstractEditForm { private ComboBox diseaseField; private ComboBox testResultField; private TextField testTypeTextField; - private ComboBox pcrTestSpecification; private Disease disease; private TextField typingIdField; @@ -282,7 +280,6 @@ public void setHeading(String heading) { public void setValue(PathogenTestDto newFieldValue) throws ReadOnlyException, Converter.ConversionException { super.setValue(newFieldValue); testTypeField.setValue(newFieldValue.getTestType()); - pcrTestSpecification.setValue(newFieldValue.getPcrTestSpecification()); testTypeTextField.setValue(newFieldValue.getTestTypeText()); if (!testResultField.isReadOnly()) { testResultField.setValue(newFieldValue.getTestResult()); @@ -315,7 +312,6 @@ protected void addFields() { testTypeField = addField(PathogenTestDto.TEST_TYPE, ComboBox.class); testTypeField.setItemCaptionMode(ItemCaptionMode.ID_TOSTRING); testTypeField.setImmediate(true); - pcrTestSpecification = addField(PathogenTestDto.PCR_TEST_SPECIFICATION, ComboBox.class); testTypeTextField = addField(PathogenTestDto.TEST_TYPE_TEXT, TextField.class); FieldHelper.addSoftRequiredStyle(testTypeTextField); DateTimeField testDateField = addField(PathogenTestDto.TEST_DATE_TIME, DateTimeField.class); @@ -469,13 +465,10 @@ protected void addFields() { addField(PathogenTestDto.OTHER_DELETION_REASON, TextArea.class).setRows(3); setVisible(false, PathogenTestDto.DELETION_REASON, PathogenTestDto.OTHER_DELETION_REASON); - pcrTestSpecification.setVisible(false); - Label prescriberHeadingLabel = new Label(I18nProperties.getCaption(Captions.PathogenTest_prescriber)); prescriberHeadingLabel.addStyleName(H3); getContent().addComponent(prescriberHeadingLabel, PRESCRIBER_HEADING_LOC); - FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.PCR_TEST_SPECIFICATION, PCR_TEST_SPECIFICATION_VISIBILITY_CONDITIONS, true); FieldHelper.setVisibleWhen( getFieldGroup(), PathogenTestDto.TEST_TYPE_TEXT, From 8c73a2ccbe290cee8660ab90d14be4a73acb0c58 Mon Sep 17 00:00:00 2001 From: Harold Asiimwe Date: Thu, 12 Mar 2026 14:57:09 +0300 Subject: [PATCH 08/30] Move disease sections to sub-package, create tube field handler for TB and clean up the PathogenTestForm --- .../sormas/ui/samples/PathogenTestForm.java | 151 ++++++++---------- .../CoronavirusDiseaseSectionLayout.java | 33 ++-- ...CryptosporidiosisDiseaseSectionLayout.java | 16 +- .../CsmDiseaseSectionLayout.java | 10 +- .../DefaultDiseaseSectionLayout.java | 8 +- .../DiseaseSectionLayout.java | 15 +- .../ImiDiseaseSectionLayout.java | 19 +-- .../IpiDiseaseSectionLayout.java | 19 +-- .../MeaslesDiseaseSectionLayout.java | 16 +- .../PathogenTestFormConfig.java | 40 +++++ .../TubeFieldHandler.java | 2 +- .../TuberculosisDiseaseSectionLayout.java | 62 ++++--- .../symeda/sormas/ui/utils/AbstractForm.java | 17 ++ 13 files changed, 218 insertions(+), 190 deletions(-) rename sormas-ui/src/main/java/de/symeda/sormas/ui/samples/{ => diseasesection}/CoronavirusDiseaseSectionLayout.java (74%) rename sormas-ui/src/main/java/de/symeda/sormas/ui/samples/{ => diseasesection}/CryptosporidiosisDiseaseSectionLayout.java (92%) rename sormas-ui/src/main/java/de/symeda/sormas/ui/samples/{ => diseasesection}/CsmDiseaseSectionLayout.java (94%) rename sormas-ui/src/main/java/de/symeda/sormas/ui/samples/{ => diseasesection}/DefaultDiseaseSectionLayout.java (92%) rename sormas-ui/src/main/java/de/symeda/sormas/ui/samples/{ => diseasesection}/DiseaseSectionLayout.java (90%) rename sormas-ui/src/main/java/de/symeda/sormas/ui/samples/{ => diseasesection}/ImiDiseaseSectionLayout.java (90%) rename sormas-ui/src/main/java/de/symeda/sormas/ui/samples/{ => diseasesection}/IpiDiseaseSectionLayout.java (91%) rename sormas-ui/src/main/java/de/symeda/sormas/ui/samples/{ => diseasesection}/MeaslesDiseaseSectionLayout.java (93%) create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/PathogenTestFormConfig.java rename sormas-ui/src/main/java/de/symeda/sormas/ui/samples/{ => diseasesection}/TubeFieldHandler.java (99%) rename sormas-ui/src/main/java/de/symeda/sormas/ui/samples/{ => diseasesection}/TuberculosisDiseaseSectionLayout.java (86%) diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java index 87207e7d93a..854457f5362 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java @@ -47,7 +47,6 @@ import com.vaadin.v7.ui.TextArea; import com.vaadin.v7.ui.TextField; -import de.symeda.sormas.api.CountryHelper; import de.symeda.sormas.api.Disease; import de.symeda.sormas.api.DiseaseHelper; import de.symeda.sormas.api.FacadeProvider; @@ -60,13 +59,15 @@ import de.symeda.sormas.api.i18n.Validations; import de.symeda.sormas.api.infrastructure.facility.FacilityDto; import de.symeda.sormas.api.infrastructure.facility.FacilityReferenceDto; -import de.symeda.sormas.api.sample.PathogenStrainCallStatus; import de.symeda.sormas.api.sample.PathogenTestDto; import de.symeda.sormas.api.sample.PathogenTestResultType; import de.symeda.sormas.api.sample.PathogenTestType; import de.symeda.sormas.api.sample.SampleDto; import de.symeda.sormas.api.sample.SamplePurpose; import de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers; +import de.symeda.sormas.ui.samples.diseasesection.DefaultDiseaseSectionLayout; +import de.symeda.sormas.ui.samples.diseasesection.DiseaseSectionLayout; +import de.symeda.sormas.ui.samples.diseasesection.PathogenTestFormConfig; import de.symeda.sormas.ui.utils.AbstractEditForm; import de.symeda.sormas.ui.utils.CssStyles; import de.symeda.sormas.ui.utils.DateComparisonValidator; @@ -149,14 +150,6 @@ public class PathogenTestForm extends AbstractEditForm { } }); - public static final Map> PCR_TEST_SPECIFICATION_VISIBILITY_CONDITIONS = Collections.unmodifiableMap(new HashMap<>() { - - { - put(PathogenTestDto.TESTED_DISEASE, Collections.unmodifiableList(Arrays.asList(Disease.CORONAVIRUS))); - put(PathogenTestDto.TEST_TYPE, Collections.unmodifiableList(Arrays.asList(PathogenTestType.PCR_RT_PCR))); - } - }); - private SampleDto sample; private EnvironmentSampleDto environmentSample; private AbstractSampleForm sampleForm; @@ -172,9 +165,22 @@ public class PathogenTestForm extends AbstractEditForm { private Disease disease; private TextField typingIdField; + // New instance fields promoted from addFields() locals + private CheckBox viaLimsField; + private ComboBox lab; + private TextField labDetails; + private TextField cqValueField; + private NullableOptionGroup testResultVerifiedField; + private CheckBox fourFoldIncrease; + private Label prescriberHeadingLabel; + private ComboBox diseaseVariantField; + private TextField diseaseVariantDetailsField; + private Consumer updateDiseaseVariantField; + // Disease section swap support private DiseaseSectionLayout activeSection = new DefaultDiseaseSectionLayout(); private CustomLayout diseaseSectionPanel; + private PathogenTestFormConfig formConfig; public PathogenTestForm( AbstractSampleForm sampleForm, @@ -294,6 +300,19 @@ public void setValue(PathogenTestDto newFieldValue) throws ReadOnlyException, Co @Override protected void addFields() { + addHeaderAndLayoutFields(); + addTestDateField(); + addLabFields(); + addDiseaseAndResultFields(); + addCtValueFields(); + addPrescriberFields(); + bindVisibilityRules(); + bindValueChangeListeners(); + finalizeForm(); + } + + private void addHeaderAndLayoutFields() { + formConfig = PathogenTestFormConfig.fromCurrentConfig(); pathogenTestHeadingLabel = new Label(); pathogenTestHeadingLabel.addStyleName(H3); @@ -306,7 +325,7 @@ protected void addFields() { getContent().addComponent(diseaseSectionPanel, DISEASE_SECTION_LOC); addDateField(PathogenTestDto.REPORT_DATE, DateField.class, 0); - CheckBox viaLimsField = addField(PathogenTestDto.VIA_LIMS); + viaLimsField = addField(PathogenTestDto.VIA_LIMS); addField(PathogenTestDto.EXTERNAL_ID); addField(PathogenTestDto.EXTERNAL_ORDER_ID); testTypeField = addField(PathogenTestDto.TEST_TYPE, ComboBox.class); @@ -314,6 +333,9 @@ protected void addFields() { testTypeField.setImmediate(true); testTypeTextField = addField(PathogenTestDto.TEST_TYPE_TEXT, TextField.class); FieldHelper.addSoftRequiredStyle(testTypeTextField); + } + + private DateTimeField addTestDateField() { DateTimeField testDateField = addField(PathogenTestDto.TEST_DATE_TIME, DateTimeField.class); testDateField.removeAllValidators(); testDateField.addValidator( @@ -351,20 +373,26 @@ protected void addFields() { DateFormatHelper.formatLocalDateTime(getSampleDate())))); }); - ComboBox lab = addInfrastructureField(PathogenTestDto.LAB); + return testDateField; + } + + private void addLabFields() { + lab = addInfrastructureField(PathogenTestDto.LAB); lab.addItems(FacadeProvider.getFacilityFacade().getAllActiveLaboratories(true)); - TextField labDetails = addField(PathogenTestDto.LAB_DETAILS, TextField.class); + labDetails = addField(PathogenTestDto.LAB_DETAILS, TextField.class); labDetails.setVisible(false); typingIdField = addField(PathogenTestDto.TYPING_ID, TextField.class); typingIdField.setVisible(false); + } - // Tested Desease or Tested Pathogen, depending on sample type + private void addDiseaseAndResultFields() { + // Tested Disease or Tested Pathogen, depending on sample type diseaseField = addDiseaseField(PathogenTestDto.TESTED_DISEASE, true, create, false); addField(PathogenTestDto.TESTED_DISEASE_DETAILS, TextField.class); - ComboBox diseaseVariantField = addCustomizableEnumField(PathogenTestDto.TESTED_DISEASE_VARIANT); + diseaseVariantField = addCustomizableEnumField(PathogenTestDto.TESTED_DISEASE_VARIANT); diseaseVariantField.setNullSelectionAllowed(true); diseaseVariantField.setVisible(false); - TextField diseaseVariantDetailsField = addField(PathogenTestDto.TESTED_DISEASE_VARIANT_DETAILS, TextField.class); + diseaseVariantDetailsField = addField(PathogenTestDto.TESTED_DISEASE_VARIANT_DETAILS, TextField.class); diseaseVariantDetailsField.setVisible(false); if (DiseaseHelper.SUBTYPE_ALLOWED_DISEASES.contains(disease)) { diseaseVariantField.setCaption(I18nProperties.getCaption(Captions.PathogenTest_rsv_testedDiseaseVariant)); @@ -402,16 +430,18 @@ protected void addFields() { testResultField = addField(PathogenTestDto.TEST_RESULT, ComboBox.class); testResultField.removeItem(PathogenTestResultType.NOT_DONE); - if (!FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { + if (!formConfig.isLuxembourg) { testResultField.removeItem(PathogenTestResultType.NOT_APPLICABLE); } // Bind the initial disease section (default = no-op; swapped via swapDiseaseSection() on disease change) activeSection = DiseaseSectionLayout.forDisease(disease); diseaseSectionPanel.setTemplateContents(activeSection.getHtmlLayout()); - activeSection.bindFields(getFieldGroup(), diseaseSectionPanel, disease); + activeSection.bindFields(getFieldGroup(), diseaseSectionPanel, disease, formConfig); + } - TextField cqValueField = addField(FieldConfiguration.withConversionError(PathogenTestDto.CQ_VALUE, Validations.onlyNumbersAllowed)); - if (!FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { + private void addCtValueFields() { + cqValueField = addField(FieldConfiguration.withConversionError(PathogenTestDto.CQ_VALUE, Validations.onlyNumbersAllowed)); + if (!formConfig.isLuxembourg) { cqValueField.setVisible(false); } @@ -432,8 +462,10 @@ protected void addFields() { PathogenTestDto.CT_VALUE_S, PathogenTestDto.CT_VALUE_ORF_1, PathogenTestDto.CT_VALUE_RDRP_S); + } - NullableOptionGroup testResultVerifiedField = addField(PathogenTestDto.TEST_RESULT_VERIFIED, NullableOptionGroup.class); + private void addPrescriberFields() { + testResultVerifiedField = addField(PathogenTestDto.TEST_RESULT_VERIFIED, NullableOptionGroup.class); addField(PathogenTestDto.PRELIMINARY).addStyleName(CssStyles.VSPACE_4); // Make TEST_RESULT_VERIFIED required only when the test comes via LIMS (laboratory is directly connected) @@ -445,7 +477,7 @@ protected void addFields() { // Set initial required state based on current viaLims value testResultVerifiedField.setRequired(Boolean.TRUE.equals(viaLimsField.getValue())); - CheckBox fourFoldIncrease = addField(PathogenTestDto.FOUR_FOLD_INCREASE_ANTIBODY_TITER, CheckBox.class); + fourFoldIncrease = addField(PathogenTestDto.FOUR_FOLD_INCREASE_ANTIBODY_TITER, CheckBox.class); CssStyles.style(fourFoldIncrease, VSPACE_3, VSPACE_TOP_4); fourFoldIncrease.setVisible(false); fourFoldIncrease.setEnabled(false); @@ -465,10 +497,12 @@ protected void addFields() { addField(PathogenTestDto.OTHER_DELETION_REASON, TextArea.class).setRows(3); setVisible(false, PathogenTestDto.DELETION_REASON, PathogenTestDto.OTHER_DELETION_REASON); - Label prescriberHeadingLabel = new Label(I18nProperties.getCaption(Captions.PathogenTest_prescriber)); + prescriberHeadingLabel = new Label(I18nProperties.getCaption(Captions.PathogenTest_prescriber)); prescriberHeadingLabel.addStyleName(H3); getContent().addComponent(prescriberHeadingLabel, PRESCRIBER_HEADING_LOC); + } + private void bindVisibilityRules() { FieldHelper.setVisibleWhen( getFieldGroup(), PathogenTestDto.TEST_TYPE_TEXT, @@ -505,17 +539,18 @@ protected void addFields() { }; FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.TESTED_DISEASE_VARIANT, diseaseVariantDependencies, true); - Consumer updateDiseaseVariantField = disease -> { - List diseaseVariants = - FacadeProvider.getCustomizableEnumFacade().getEnumValues(CustomizableEnumType.DISEASE_VARIANT, disease); + updateDiseaseVariantField = d -> { + List diseaseVariants = FacadeProvider.getCustomizableEnumFacade().getEnumValues(CustomizableEnumType.DISEASE_VARIANT, d); FieldHelper.updateItems(diseaseVariantField, diseaseVariants); - diseaseVariantField.setVisible( - disease != null && isVisibleAllowed(PathogenTestDto.TESTED_DISEASE_VARIANT) && CollectionUtils.isNotEmpty(diseaseVariants)); + diseaseVariantField + .setVisible(d != null && isVisibleAllowed(PathogenTestDto.TESTED_DISEASE_VARIANT) && CollectionUtils.isNotEmpty(diseaseVariants)); }; updateDiseaseVariantField.accept((Disease) diseaseField.getValue()); + } - diseaseField.addValueChangeListener((ValueChangeListener) valueChangeEvent -> { + private void bindValueChangeListeners() { + diseaseField.addValueChangeListener(valueChangeEvent -> { Disease latestDisease = (Disease) valueChangeEvent.getProperty().getValue(); // If the disease changed, test type field should be updated with its respective test types if (latestDisease != disease) { @@ -530,22 +565,6 @@ protected void addFields() { Arrays.asList(PathogenTestType.values()), FieldVisibilityCheckers.withDisease(disease), PathogenTestType.class); - - if (FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { - ComboBox strainCallStatusField = getField(PathogenTestDto.STRAIN_CALL_STATUS); - if (strainCallStatusField != null) { - FieldHelper.updateItems( - strainCallStatusField, - Arrays.asList(PathogenStrainCallStatus.values()), - FieldVisibilityCheckers.withDisease(disease), - PathogenStrainCallStatus.class); - } - - activeSection.onTestTypeChanged( - (PathogenTestType) testTypeField.getValue(), - disease, - (com.vaadin.v7.ui.AbstractField) (Object) testResultField); - } }); diseaseVariantField.addValueChangeListener(e -> { DiseaseVariant diseaseVariant = (DiseaseVariant) e.getProperty().getValue(); @@ -589,34 +608,7 @@ protected void addFields() { PathogenTestDto.CT_VALUE_ORF_1, PathogenTestDto.CT_VALUE_RDRP_S); } - // Show tube IGRA fields only for IGRA tests, Luxembourg, and TB section - if (activeSection instanceof TuberculosisDiseaseSectionLayout) { - setVisibleClear( - PathogenTestType.IGRA == testType - && FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG), - PathogenTestDto.TUBE_NIL, - PathogenTestDto.TUBE_NIL_GT10, - PathogenTestDto.TUBE_AG_TB1, - PathogenTestDto.TUBE_AG_TB1_GT10, - PathogenTestDto.TUBE_AG_TB2, - PathogenTestDto.TUBE_AG_TB2_GT10, - PathogenTestDto.TUBE_MITOGENE, - PathogenTestDto.TUBE_MITOGENE_GT10); - } } else { - // hide tube fields when no test type selected (TB section only) - if (activeSection instanceof TuberculosisDiseaseSectionLayout) { - setVisibleClear( - false, - PathogenTestDto.TUBE_NIL, - PathogenTestDto.TUBE_NIL_GT10, - PathogenTestDto.TUBE_AG_TB1, - PathogenTestDto.TUBE_AG_TB1_GT10, - PathogenTestDto.TUBE_AG_TB2, - PathogenTestDto.TUBE_AG_TB2_GT10, - PathogenTestDto.TUBE_MITOGENE, - PathogenTestDto.TUBE_MITOGENE_GT10); - } testResultField.clear(); testResultField.setEnabled(true); } @@ -630,12 +622,11 @@ protected void addFields() { activeSection.onTestTypeChanged( testType, (Disease) diseaseField.getValue(), - (com.vaadin.v7.ui.AbstractField) (Object) testResultField); + (com.vaadin.v7.ui.AbstractField) (Object) testResultField, + formConfig); }); - lab.addValueChangeListener(event -> - - { + lab.addValueChangeListener(event -> { if (event.getProperty().getValue() != null && ((FacilityReferenceDto) event.getProperty().getValue()).getUuid().equals(FacilityDto.OTHER_FACILITY_UUID)) { labDetails.setVisible(true); @@ -656,7 +647,9 @@ protected void addFields() { PathogenTestResultType testResult = (PathogenTestResultType) e.getProperty().getValue(); setCqValueVisibility(diseaseField, cqValueField, (PathogenTestType) testTypeField.getValue(), testResult); }); + } + private void finalizeForm() { if (SamplePurpose.INTERNAL.equals(getSamplePurpose())) { // this only works for already saved samples setRequired(true, PathogenTestDto.LAB); } @@ -677,10 +670,6 @@ protected void addFields() { || isVisibleAllowed(PathogenTestDto.PRESCRIBER_COUNTRY)); } - // ----------------------------------------------------------------------- - // Disease section swap support - // ----------------------------------------------------------------------- - /** Replaces the active disease section with the one appropriate for the given disease. */ private void swapDiseaseSection(Disease newDisease) { DiseaseSectionLayout newSection = DiseaseSectionLayout.forDisease(newDisease); @@ -692,7 +681,7 @@ private void swapDiseaseSection(Disease newDisease) { activeSection.unbindFields(getFieldGroup(), diseaseSectionPanel); activeSection = newSection; diseaseSectionPanel.setTemplateContents(newSection.getHtmlLayout()); - newSection.bindFields(getFieldGroup(), diseaseSectionPanel, newDisease); + newSection.bindFields(getFieldGroup(), diseaseSectionPanel, newDisease, formConfig); rebuildSectionFieldAllowances(newSection.getFieldIds()); } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/CoronavirusDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CoronavirusDiseaseSectionLayout.java similarity index 74% rename from sormas-ui/src/main/java/de/symeda/sormas/ui/samples/CoronavirusDiseaseSectionLayout.java rename to sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CoronavirusDiseaseSectionLayout.java index 821e770d305..9fa2e317c6b 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/CoronavirusDiseaseSectionLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CoronavirusDiseaseSectionLayout.java @@ -15,12 +15,16 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . *******************************************************************************/ -package de.symeda.sormas.ui.samples; +package de.symeda.sormas.ui.samples.diseasesection; import static de.symeda.sormas.ui.utils.LayoutUtil.fluidRowLocs; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import com.vaadin.ui.CustomLayout; import com.vaadin.v7.data.fieldgroup.FieldGroup; @@ -29,6 +33,7 @@ import de.symeda.sormas.api.Disease; import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.api.sample.PathogenTestType; import de.symeda.sormas.ui.utils.FieldHelper; /** @@ -42,6 +47,14 @@ public class CoronavirusDiseaseSectionLayout implements DiseaseSectionLayout { private static final Collection FIELD_IDS = Collections.singletonList(PathogenTestDto.PCR_TEST_SPECIFICATION); + static final Map> PCR_TEST_SPECIFICATION_VISIBILITY_CONDITIONS = Collections.unmodifiableMap(new HashMap<>() { + + { + put(PathogenTestDto.TESTED_DISEASE, Collections.unmodifiableList(Arrays.asList(Disease.CORONAVIRUS))); + put(PathogenTestDto.TEST_TYPE, Collections.unmodifiableList(Arrays.asList(PathogenTestType.PCR_RT_PCR))); + } + }); + @Override public String getHtmlLayout() { return HTML; @@ -53,16 +66,14 @@ public Collection getFieldIds() { } @Override - public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease) { - ComboBox pcrTestSpecification = fieldGroup.buildAndBind( - PathogenTestDto.PCR_TEST_SPECIFICATION, - (Object) PathogenTestDto.PCR_TEST_SPECIFICATION, - ComboBox.class); + public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease, PathogenTestFormConfig config) { + ComboBox pcrTestSpecification = + fieldGroup.buildAndBind(PathogenTestDto.PCR_TEST_SPECIFICATION, (Object) PathogenTestDto.PCR_TEST_SPECIFICATION, ComboBox.class); pcrTestSpecification.setId(PathogenTestDto.PCR_TEST_SPECIFICATION); pcrTestSpecification.setVisible(false); panel.addComponent(pcrTestSpecification, PathogenTestDto.PCR_TEST_SPECIFICATION); - FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.PCR_TEST_SPECIFICATION, PathogenTestForm.PCR_TEST_SPECIFICATION_VISIBILITY_CONDITIONS, true); + FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.PCR_TEST_SPECIFICATION, PCR_TEST_SPECIFICATION_VISIBILITY_CONDITIONS, true); } @Override @@ -73,10 +84,4 @@ public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { panel.removeComponent(field); } } - - @Override - public Disease[] getDiseases() { - return new Disease[] { - Disease.CORONAVIRUS }; - } -} \ No newline at end of file +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/CryptosporidiosisDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CryptosporidiosisDiseaseSectionLayout.java similarity index 92% rename from sormas-ui/src/main/java/de/symeda/sormas/ui/samples/CryptosporidiosisDiseaseSectionLayout.java rename to sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CryptosporidiosisDiseaseSectionLayout.java index 77dadd561e4..444573a44b3 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/CryptosporidiosisDiseaseSectionLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CryptosporidiosisDiseaseSectionLayout.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . *******************************************************************************/ -package de.symeda.sormas.ui.samples; +package de.symeda.sormas.ui.samples.diseasesection; import java.util.Arrays; import java.util.Collection; @@ -60,7 +60,7 @@ public Collection getFieldIds() { } @Override - public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease) { + public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease, PathogenTestFormConfig config) { Map> genoTypingDependencies = new HashMap<>(); genoTypingDependencies.put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.MEASLES, Disease.CRYPTOSPORIDIOSIS)); genoTypingDependencies.put(PathogenTestDto.TEST_TYPE, Arrays.asList(PathogenTestType.GENOTYPING)); @@ -93,13 +93,11 @@ public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { } @Override - public void onTestTypeChanged(PathogenTestType testType, Disease disease, AbstractField testResultField) { + public void onTestTypeChanged( + PathogenTestType testType, + Disease disease, + AbstractField testResultField, + PathogenTestFormConfig config) { // genotype item update is handled by the registered testTypeListener } - - @Override - public Disease[] getDiseases() { - return new Disease[] { - Disease.CRYPTOSPORIDIOSIS }; - } } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/CsmDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CsmDiseaseSectionLayout.java similarity index 94% rename from sormas-ui/src/main/java/de/symeda/sormas/ui/samples/CsmDiseaseSectionLayout.java rename to sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CsmDiseaseSectionLayout.java index 7ef40440b89..3bf039d0d57 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/CsmDiseaseSectionLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CsmDiseaseSectionLayout.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . *******************************************************************************/ -package de.symeda.sormas.ui.samples; +package de.symeda.sormas.ui.samples.diseasesection; import java.util.Arrays; import java.util.Collection; @@ -51,7 +51,7 @@ public Collection getFieldIds() { } @Override - public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease) { + public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease, PathogenTestFormConfig config) { Map> serotypeVisibilityDependencies = new HashMap<>(); serotypeVisibilityDependencies.put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.CSM)); serotypeVisibilityDependencies.put(PathogenTestDto.TEST_RESULT, Arrays.asList(PathogenTestResultType.POSITIVE)); @@ -65,10 +65,4 @@ public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { // multi-condition visibility logic is harmless when this disease is not active, // because the TESTED_DISEASE condition will never match CSM for another section. } - - @Override - public Disease[] getDiseases() { - return new Disease[] { - Disease.CSM }; - } } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/DefaultDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/DefaultDiseaseSectionLayout.java similarity index 92% rename from sormas-ui/src/main/java/de/symeda/sormas/ui/samples/DefaultDiseaseSectionLayout.java rename to sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/DefaultDiseaseSectionLayout.java index e6e1154450f..1ceded47d8c 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/DefaultDiseaseSectionLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/DefaultDiseaseSectionLayout.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . *******************************************************************************/ -package de.symeda.sormas.ui.samples; +package de.symeda.sormas.ui.samples.diseasesection; import java.util.Collection; import java.util.Collections; @@ -41,7 +41,7 @@ public Collection getFieldIds() { } @Override - public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease) { + public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease, PathogenTestFormConfig config) { // no disease-specific fields } @@ -50,8 +50,4 @@ public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { // nothing to remove } - @Override - public Disease[] getDiseases() { - return new Disease[0]; - } } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/DiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/DiseaseSectionLayout.java similarity index 90% rename from sormas-ui/src/main/java/de/symeda/sormas/ui/samples/DiseaseSectionLayout.java rename to sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/DiseaseSectionLayout.java index a9d3123c82c..7bc707918e3 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/DiseaseSectionLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/DiseaseSectionLayout.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . *******************************************************************************/ -package de.symeda.sormas.ui.samples; +package de.symeda.sormas.ui.samples.diseasesection; import java.util.Collection; @@ -36,7 +36,7 @@ * Lifecycle: *
      *
    1. {@link #getHtmlLayout()} is called once to build the section's CustomLayout template.
    2. - *
    3. {@link #bindFields(FieldGroup, CustomLayout, Disease)} is called to add fields.
    4. + *
    5. {@link #bindFields(FieldGroup, CustomLayout, Disease, PathogenTestFormConfig)} is called to add fields.
    6. *
    7. {@link #unbindFields(FieldGroup, CustomLayout)} is called before a new section replaces this one.
    8. *
    */ @@ -55,7 +55,7 @@ public interface DiseaseSectionLayout { * Adds this section's fields to the given field group and panel. * Called once after the section's CustomLayout has been installed. */ - void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease); + void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease, PathogenTestFormConfig config); /** * Removes this section's fields from the field group and panel, @@ -68,12 +68,13 @@ public interface DiseaseSectionLayout { * Called when the test type changes so sections can update field visibility or * auto-set the test result. No-op by default. */ - default void onTestTypeChanged(PathogenTestType testType, Disease disease, AbstractField testResultField) { + default void onTestTypeChanged( + PathogenTestType testType, + Disease disease, + AbstractField testResultField, + PathogenTestFormConfig config) { } - /** Returns the disease(s) this section handles. */ - Disease[] getDiseases(); - /** Factory: returns the correct section implementation for the given disease. */ static DiseaseSectionLayout forDisease(Disease disease) { if (disease == null) { diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/ImiDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/ImiDiseaseSectionLayout.java similarity index 90% rename from sormas-ui/src/main/java/de/symeda/sormas/ui/samples/ImiDiseaseSectionLayout.java rename to sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/ImiDiseaseSectionLayout.java index 6f8cbf9380f..254ee3e2b4c 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/ImiDiseaseSectionLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/ImiDiseaseSectionLayout.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . *******************************************************************************/ -package de.symeda.sormas.ui.samples; +package de.symeda.sormas.ui.samples.diseasesection; import java.util.Arrays; import java.util.Collection; @@ -28,7 +28,6 @@ import com.vaadin.v7.data.fieldgroup.FieldGroup; import com.vaadin.v7.ui.AbstractField; -import de.symeda.sormas.api.CountryHelper; import de.symeda.sormas.api.Disease; import de.symeda.sormas.api.FacadeProvider; import de.symeda.sormas.api.sample.PathogenTestDto; @@ -64,7 +63,7 @@ public Collection getFieldIds() { } @Override - public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease) { + public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease, PathogenTestFormConfig config) { Map> imiSeroTypingDependencies = new HashMap<>(); imiSeroTypingDependencies.put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.INVASIVE_MENINGOCOCCAL_INFECTION)); imiSeroTypingDependencies.put(PathogenTestDto.TEST_RESULT, Arrays.asList(PathogenTestResultType.POSITIVE)); @@ -100,12 +99,16 @@ public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { } @Override - public void onTestTypeChanged(PathogenTestType testType, Disease disease, AbstractField testResultField) { + public void onTestTypeChanged( + PathogenTestType testType, + Disease disease, + AbstractField testResultField, + PathogenTestFormConfig config) { if (drugSusceptibilityField != null) { drugSusceptibilityField.updateFieldsVisibility(disease, testType); } - if (testType == null || !FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { + if (testType == null || !config.isLuxembourg) { return; } @@ -113,10 +116,4 @@ public void onTestTypeChanged(PathogenTestType testType, Disease disease, Abstra testResultField.setValue(PathogenTestResultType.POSITIVE); } } - - @Override - public Disease[] getDiseases() { - return new Disease[] { - Disease.INVASIVE_MENINGOCOCCAL_INFECTION }; - } } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/IpiDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/IpiDiseaseSectionLayout.java similarity index 91% rename from sormas-ui/src/main/java/de/symeda/sormas/ui/samples/IpiDiseaseSectionLayout.java rename to sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/IpiDiseaseSectionLayout.java index 255e64da406..0bf953e75de 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/IpiDiseaseSectionLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/IpiDiseaseSectionLayout.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . *******************************************************************************/ -package de.symeda.sormas.ui.samples; +package de.symeda.sormas.ui.samples.diseasesection; import java.util.Arrays; import java.util.Collection; @@ -28,7 +28,6 @@ import com.vaadin.v7.data.fieldgroup.FieldGroup; import com.vaadin.v7.ui.AbstractField; -import de.symeda.sormas.api.CountryHelper; import de.symeda.sormas.api.Disease; import de.symeda.sormas.api.FacadeProvider; import de.symeda.sormas.api.sample.PathogenTestDto; @@ -68,7 +67,7 @@ public Collection getFieldIds() { } @Override - public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease) { + public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease, PathogenTestFormConfig config) { // serotype + serotypingMethod visible on SEROGROUPING + POSITIVE Map> serotypeAndMethodDependencies = new HashMap<>(); serotypeAndMethodDependencies.put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.INVASIVE_PNEUMOCOCCAL_INFECTION)); @@ -114,12 +113,16 @@ public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { } @Override - public void onTestTypeChanged(PathogenTestType testType, Disease disease, AbstractField testResultField) { + public void onTestTypeChanged( + PathogenTestType testType, + Disease disease, + AbstractField testResultField, + PathogenTestFormConfig config) { if (drugSusceptibilityField != null) { drugSusceptibilityField.updateFieldsVisibility(disease, testType); } - if (testType == null || !FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { + if (testType == null || !config.isLuxembourg) { return; } @@ -127,10 +130,4 @@ public void onTestTypeChanged(PathogenTestType testType, Disease disease, Abstra testResultField.setValue(PathogenTestResultType.POSITIVE); } } - - @Override - public Disease[] getDiseases() { - return new Disease[] { - Disease.INVASIVE_PNEUMOCOCCAL_INFECTION }; - } } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/MeaslesDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/MeaslesDiseaseSectionLayout.java similarity index 93% rename from sormas-ui/src/main/java/de/symeda/sormas/ui/samples/MeaslesDiseaseSectionLayout.java rename to sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/MeaslesDiseaseSectionLayout.java index 4fe509a349a..5e0bf73a33b 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/MeaslesDiseaseSectionLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/MeaslesDiseaseSectionLayout.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . *******************************************************************************/ -package de.symeda.sormas.ui.samples; +package de.symeda.sormas.ui.samples.diseasesection; import java.util.Arrays; import java.util.Collection; @@ -60,7 +60,7 @@ public Collection getFieldIds() { } @Override - public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease) { + public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease, PathogenTestFormConfig config) { Map> genoTypingDependencies = new HashMap<>(); genoTypingDependencies.put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.MEASLES, Disease.CRYPTOSPORIDIOSIS)); genoTypingDependencies.put(PathogenTestDto.TEST_TYPE, Arrays.asList(PathogenTestType.GENOTYPING)); @@ -94,13 +94,11 @@ public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { } @Override - public void onTestTypeChanged(PathogenTestType testType, Disease disease, AbstractField testResultField) { + public void onTestTypeChanged( + PathogenTestType testType, + Disease disease, + AbstractField testResultField, + PathogenTestFormConfig config) { // genotype item update is handled by the registered testTypeListener } - - @Override - public Disease[] getDiseases() { - return new Disease[] { - Disease.MEASLES }; - } } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/PathogenTestFormConfig.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/PathogenTestFormConfig.java new file mode 100644 index 00000000000..3e3b7c5be3b --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/PathogenTestFormConfig.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2018 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * + * 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.samples.diseasesection; + +import de.symeda.sormas.api.CountryHelper; +import de.symeda.sormas.api.FacadeProvider; + +/** + * Immutable snapshot of country-specific feature flags for PathogenTestForm. + * Computed once at form construction time so section classes never call + * FacadeProvider directly for country checks. + */ +public class PathogenTestFormConfig { + + public final boolean isLuxembourg; + + private PathogenTestFormConfig(boolean isLuxembourg) { + this.isLuxembourg = isLuxembourg; + } + + public static PathogenTestFormConfig fromCurrentConfig() { + return new PathogenTestFormConfig( + FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)); + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TubeFieldHandler.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/TubeFieldHandler.java similarity index 99% rename from sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TubeFieldHandler.java rename to sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/TubeFieldHandler.java index f50226513b9..477e89a25ff 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TubeFieldHandler.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/TubeFieldHandler.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . *******************************************************************************/ -package de.symeda.sormas.ui.samples; +package de.symeda.sormas.ui.samples.diseasesection; import java.util.Collection; diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TuberculosisDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/TuberculosisDiseaseSectionLayout.java similarity index 86% rename from sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TuberculosisDiseaseSectionLayout.java rename to sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/TuberculosisDiseaseSectionLayout.java index 5d662b71ea0..c4af635cdd6 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/TuberculosisDiseaseSectionLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/TuberculosisDiseaseSectionLayout.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . *******************************************************************************/ -package de.symeda.sormas.ui.samples; +package de.symeda.sormas.ui.samples.diseasesection; import static de.symeda.sormas.ui.utils.LayoutUtil.fluidRowLocs; @@ -35,7 +35,6 @@ import com.vaadin.v7.ui.Field; import com.vaadin.v7.ui.TextField; -import de.symeda.sormas.api.CountryHelper; import de.symeda.sormas.api.Disease; import de.symeda.sormas.api.FacadeProvider; import de.symeda.sormas.api.sample.PathogenStrainCallStatus; @@ -123,6 +122,7 @@ public class TuberculosisDiseaseSectionLayout implements DiseaseSectionLayout { private DrugSusceptibilityForm drugSusceptibilityField; private TubeFieldHandler tubeFieldHandler; private List tubeFieldIds; + private FieldGroup boundFieldGroup; // Listeners registered on shared FieldGroup source fields — removed in unbindFields private Property.ValueChangeListener testTypeListener; @@ -140,7 +140,7 @@ public Collection getFieldIds() { } @Override - public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease) { + public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease, PathogenTestFormConfig config) { NullableOptionGroup rifampicinResistant = buildAndAdd(fieldGroup, panel, PathogenTestDto.RIFAMPICIN_RESISTANT, NullableOptionGroup.class); rifampicinResistant.setVisible(false); @@ -180,27 +180,20 @@ public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease diseas tubeFieldHandler = new TubeFieldHandler(fieldGroup, panel); tubeFieldHandler.attachTubeFields(); - if (FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { - bindLuxembourgVisibility(fieldGroup, strainCallStatus, disease); + boundFieldGroup = fieldGroup; + + if (config.isLuxembourg) { + bindLuxembourgVisibility(fieldGroup, strainCallStatus, disease, config.isLuxembourg); } } - private void bindLuxembourgVisibility(FieldGroup fieldGroup, ComboBox strainCallStatus, Disease disease) { + private void bindLuxembourgVisibility(FieldGroup fieldGroup, ComboBox strainCallStatus, Disease disease, boolean isLuxembourg) { // Named listeners so they can be removed in unbindFields - testTypeListener = e -> { - Field testTypeField = fieldGroup.getField(PathogenTestDto.TEST_TYPE); - Field testResultField = fieldGroup.getField(PathogenTestDto.TEST_RESULT); - onValueChangedSetVisible(fieldGroup, testTypeField, testResultField); - }; - testResultListener = e -> { - Field testTypeField = fieldGroup.getField(PathogenTestDto.TEST_TYPE); - Field testResultField = fieldGroup.getField(PathogenTestDto.TEST_RESULT); - onValueChangedSetVisible(fieldGroup, testTypeField, testResultField); - }; + testTypeListener = e -> setTubeFieldsVisible(fieldGroup, (PathogenTestType) e.getProperty().getValue(), isLuxembourg); + testResultListener = + e -> setTubeFieldsVisible(fieldGroup, (PathogenTestType) fieldGroup.getField(PathogenTestDto.TEST_TYPE).getValue(), isLuxembourg); diseaseListener = e -> { - Field testTypeField = fieldGroup.getField(PathogenTestDto.TEST_TYPE); - Field testResultField = fieldGroup.getField(PathogenTestDto.TEST_RESULT); - onValueChangedSetVisible(fieldGroup, testTypeField, testResultField); + setTubeFieldsVisible(fieldGroup, (PathogenTestType) fieldGroup.getField(PathogenTestDto.TEST_TYPE).getValue(), isLuxembourg); Disease newDisease = (Disease) e.getProperty().getValue(); FieldHelper.updateItems( @@ -232,13 +225,14 @@ private void bindLuxembourgVisibility(FieldGroup fieldGroup, ComboBox strainCall PathogenStrainCallStatus.class); // Apply initial visibility - onValueChangedSetVisible(fieldGroup, fieldGroup.getField(PathogenTestDto.TEST_TYPE), fieldGroup.getField(PathogenTestDto.TEST_RESULT)); + setTubeFieldsVisible(fieldGroup, (PathogenTestType) fieldGroup.getField(PathogenTestDto.TEST_TYPE).getValue(), isLuxembourg); } - private void onValueChangedSetVisible(FieldGroup fieldGroup, Field testTypeField, Field testResultField) { - PathogenTestType testType = testTypeField != null ? (PathogenTestType) testTypeField.getValue() : null; - boolean showTubes = - testType == PathogenTestType.IGRA && FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG); + private void setTubeFieldsVisible(FieldGroup fieldGroup, PathogenTestType testType, boolean isLuxembourg) { + if (tubeFieldIds == null) { + return; + } + boolean showTubes = testType == PathogenTestType.IGRA && isLuxembourg; for (String tubeId : tubeFieldIds) { Field f = fieldGroup.getField(tubeId); if (f != null) { @@ -290,15 +284,24 @@ public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { drugSusceptibilityField = null; tubeFieldIds = null; + boundFieldGroup = null; } @Override - public void onTestTypeChanged(PathogenTestType testType, Disease disease, AbstractField testResultField) { + public void onTestTypeChanged( + PathogenTestType testType, + Disease disease, + AbstractField testResultField, + PathogenTestFormConfig config) { if (drugSusceptibilityField != null) { drugSusceptibilityField.updateFieldsVisibility(disease, testType); } - if (testType == null || !FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { + if (boundFieldGroup != null && tubeFieldIds != null) { + setTubeFieldsVisible(boundFieldGroup, testType, config.isLuxembourg); + } + + if (testType == null || !config.isLuxembourg) { return; } @@ -316,13 +319,6 @@ public void onTestTypeChanged(PathogenTestType testType, Disease disease, Abstra } } - @Override - public Disease[] getDiseases() { - return new Disease[] { - Disease.TUBERCULOSIS, - Disease.LATENT_TUBERCULOSIS }; - } - private > F buildAndAdd(FieldGroup fieldGroup, CustomLayout panel, String propertyId, Class fieldType) { F field = fieldGroup.buildAndBind(propertyId, (Object) propertyId, fieldType); field.setId(propertyId); diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/AbstractForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/AbstractForm.java index 81d33f17ed5..0ebe4cb9956 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/AbstractForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/AbstractForm.java @@ -558,5 +558,22 @@ protected void configureField(Field field) { field.setReadOnly(true); } } + + /** + * Iterates a snapshot of the field collection so that value-change listeners triggered + * during discard (e.g. disease-section swap that binds/unbinds fields) cannot cause a + * ConcurrentModificationException on the underlying LinkedHashMap. + * Per-field discard failures are swallowed to match the behaviour of the super implementation. + */ + @Override + public void discard() { + for (Field f : new ArrayList<>(getFields())) { + try { + f.discard(); + } catch (Exception e) { + // intentional: matches FieldGroup.discard() original behaviour + } + } + } } } From 019e58a197b5064e4c75c4f669fc0f84e102c68e Mon Sep 17 00:00:00 2001 From: Harold Asiimwe Date: Fri, 13 Mar 2026 14:28:58 +0300 Subject: [PATCH 09/30] Resolves disease section issues as per PR comments with improvements --- .../sormas/ui/samples/PathogenTestForm.java | 14 ++- .../CoronavirusDiseaseSectionLayout.java | 26 +++-- ...CryptosporidiosisDiseaseSectionLayout.java | 41 ++++++- .../CsmDiseaseSectionLayout.java | 32 ++++- .../diseasesection/DiseaseSectionLayout.java | 40 +++++++ .../ImiDiseaseSectionLayout.java | 45 ++++++- .../IpiDiseaseSectionLayout.java | 51 +++++++- .../MeaslesDiseaseSectionLayout.java | 42 ++++++- .../diseasesection/TubeFieldHandler.java | 50 +++++--- .../TuberculosisDiseaseSectionLayout.java | 110 +++++++++--------- .../sormas/ui/utils/AbstractEditForm.java | 8 +- .../symeda/sormas/ui/utils/AbstractForm.java | 11 +- .../symeda/sormas/ui/utils/FieldHelper.java | 88 +++++++++----- .../symeda/sormas/ui/utils/Registration.java | 37 ++++++ 14 files changed, 456 insertions(+), 139 deletions(-) create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/utils/Registration.java diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java index 0bf9a772258..6c6644f6a80 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java @@ -679,16 +679,28 @@ private void swapDiseaseSection(Disease newDisease) { removeFromAllowedLists(activeSection.getFieldIds()); activeSection.unbindFields(getFieldGroup(), diseaseSectionPanel); activeSection = newSection; - diseaseSectionPanel.setTemplateContents(newSection.getHtmlLayout()); + + // Vaadin's CustomLayout.setTemplateContents() only works before the component + // is attached. Replace the entire panel so the new template renders correctly. + CustomLayout newPanel = new CustomLayout(); + newPanel.setTemplateContents(newSection.getHtmlLayout()); + newPanel.setWidth(100, Unit.PERCENTAGE); + getContent().addComponent(newPanel, DISEASE_SECTION_LOC); + diseaseSectionPanel = newPanel; + newSection.bindFields(getFieldGroup(), diseaseSectionPanel, newDisease, formConfig); rebuildSectionFieldAllowances(newSection.getFieldIds()); } + @SuppressWarnings("unchecked") private void rebuildSectionFieldAllowances(Collection fieldIds) { for (String id : fieldIds) { com.vaadin.v7.ui.Field f = getField(id); if (f != null) { addToVisibleAllowedFields(f); + if (fieldAccessCheckers == null || fieldAccessCheckers.isAccessible(getType(), id)) { + addToEditableAllowedFields(f); + } } } } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CoronavirusDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CoronavirusDiseaseSectionLayout.java index 9fa2e317c6b..3d29f8b0e52 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CoronavirusDiseaseSectionLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CoronavirusDiseaseSectionLayout.java @@ -22,7 +22,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -35,6 +34,7 @@ import de.symeda.sormas.api.sample.PathogenTestDto; import de.symeda.sormas.api.sample.PathogenTestType; import de.symeda.sormas.ui.utils.FieldHelper; +import de.symeda.sormas.ui.utils.Registration; /** * Disease section for CORONAVIRUS: owns PCR_TEST_SPECIFICATION field and wires its visibility. @@ -47,13 +47,13 @@ public class CoronavirusDiseaseSectionLayout implements DiseaseSectionLayout { private static final Collection FIELD_IDS = Collections.singletonList(PathogenTestDto.PCR_TEST_SPECIFICATION); - static final Map> PCR_TEST_SPECIFICATION_VISIBILITY_CONDITIONS = Collections.unmodifiableMap(new HashMap<>() { + private Registration visibilityRegistration; - { - put(PathogenTestDto.TESTED_DISEASE, Collections.unmodifiableList(Arrays.asList(Disease.CORONAVIRUS))); - put(PathogenTestDto.TEST_TYPE, Collections.unmodifiableList(Arrays.asList(PathogenTestType.PCR_RT_PCR))); - } - }); + static final Map> PCR_TEST_SPECIFICATION_VISIBILITY_CONDITIONS = DiseaseSectionLayout.conditionsOf( + PathogenTestDto.TESTED_DISEASE, + Arrays.asList(Disease.CORONAVIRUS), + PathogenTestDto.TEST_TYPE, + Arrays.asList(PathogenTestType.PCR_RT_PCR)); @Override public String getHtmlLayout() { @@ -67,17 +67,19 @@ public Collection getFieldIds() { @Override public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease, PathogenTestFormConfig config) { - ComboBox pcrTestSpecification = - fieldGroup.buildAndBind(PathogenTestDto.PCR_TEST_SPECIFICATION, (Object) PathogenTestDto.PCR_TEST_SPECIFICATION, ComboBox.class); - pcrTestSpecification.setId(PathogenTestDto.PCR_TEST_SPECIFICATION); + ComboBox pcrTestSpecification = buildAndAdd(fieldGroup, panel, PathogenTestDto.PCR_TEST_SPECIFICATION, ComboBox.class); pcrTestSpecification.setVisible(false); - panel.addComponent(pcrTestSpecification, PathogenTestDto.PCR_TEST_SPECIFICATION); - FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.PCR_TEST_SPECIFICATION, PCR_TEST_SPECIFICATION_VISIBILITY_CONDITIONS, true); + visibilityRegistration = + FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.PCR_TEST_SPECIFICATION, PCR_TEST_SPECIFICATION_VISIBILITY_CONDITIONS, true); } @Override public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { + if (visibilityRegistration != null) { + visibilityRegistration.remove(); + visibilityRegistration = null; + } Field field = fieldGroup.getField(PathogenTestDto.PCR_TEST_SPECIFICATION); if (field != null) { fieldGroup.unbind(field); diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CryptosporidiosisDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CryptosporidiosisDiseaseSectionLayout.java index 444573a44b3..e1398d120d1 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CryptosporidiosisDiseaseSectionLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CryptosporidiosisDiseaseSectionLayout.java @@ -17,6 +17,8 @@ *******************************************************************************/ package de.symeda.sormas.ui.samples.diseasesection; +import static de.symeda.sormas.ui.utils.LayoutUtil.fluidRowLocs; + import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -30,6 +32,7 @@ import com.vaadin.v7.ui.AbstractField; import com.vaadin.v7.ui.ComboBox; import com.vaadin.v7.ui.Field; +import com.vaadin.v7.ui.TextField; import de.symeda.sormas.api.Disease; import de.symeda.sormas.api.sample.GenoTypeResult; @@ -37,21 +40,27 @@ import de.symeda.sormas.api.sample.PathogenTestResultType; import de.symeda.sormas.api.sample.PathogenTestType; import de.symeda.sormas.ui.utils.FieldHelper; +import de.symeda.sormas.ui.utils.Registration; /** * Disease section for CRYPTOSPORIDIOSIS: same genotype result pattern as Measles. - * Fields live in the common layout; this section owns the visibility registrations. */ public class CryptosporidiosisDiseaseSectionLayout implements DiseaseSectionLayout { + //@formatter:off + private static final String HTML = + fluidRowLocs(4, PathogenTestDto.GENOTYPE_RESULT, 6, PathogenTestDto.GENOTYPE_RESULT_TEXT); + //@formatter:on + private static final List FIELD_IDS = Collections.unmodifiableList(Arrays.asList(PathogenTestDto.GENOTYPE_RESULT, PathogenTestDto.GENOTYPE_RESULT_TEXT)); private Property.ValueChangeListener testTypeListener; + private Registration visibilityRegistration; @Override public String getHtmlLayout() { - return ""; + return HTML; } @Override @@ -61,12 +70,23 @@ public Collection getFieldIds() { @Override public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease, PathogenTestFormConfig config) { + ComboBox genoTypeResult = buildAndAdd(fieldGroup, panel, PathogenTestDto.GENOTYPE_RESULT, ComboBox.class); + genoTypeResult.setVisible(false); + + TextField genoTypeResultText = buildAndAdd(fieldGroup, panel, PathogenTestDto.GENOTYPE_RESULT_TEXT, TextField.class); + genoTypeResultText.setVisible(false); + Map> genoTypingDependencies = new HashMap<>(); genoTypingDependencies.put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.MEASLES, Disease.CRYPTOSPORIDIOSIS)); genoTypingDependencies.put(PathogenTestDto.TEST_TYPE, Arrays.asList(PathogenTestType.GENOTYPING)); genoTypingDependencies.put(PathogenTestDto.TEST_RESULT, Arrays.asList(PathogenTestResultType.POSITIVE)); - FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.GENOTYPE_RESULT, genoTypingDependencies, true); - FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.GENOTYPE_RESULT_TEXT, PathogenTestDto.GENOTYPE_RESULT, GenoTypeResult.OTHER, true); + Registration r1 = FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.GENOTYPE_RESULT, genoTypingDependencies, true); + Registration r2 = + FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.GENOTYPE_RESULT_TEXT, PathogenTestDto.GENOTYPE_RESULT, GenoTypeResult.OTHER, true); + visibilityRegistration = Registration.combine(r1, r2); + + // Populate genotype items for the current disease + FieldHelper.updateItems(disease, genoTypeResult, GenoTypeResult.class); testTypeListener = e -> { Field genoTypingCB = fieldGroup.getField(PathogenTestDto.GENOTYPE_RESULT); @@ -83,6 +103,10 @@ public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease diseas @Override public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { + if (visibilityRegistration != null) { + visibilityRegistration.remove(); + visibilityRegistration = null; + } if (testTypeListener != null) { Field testTypeField = fieldGroup.getField(PathogenTestDto.TEST_TYPE); if (testTypeField != null) { @@ -90,6 +114,14 @@ public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { } testTypeListener = null; } + + for (String id : FIELD_IDS) { + Field field = fieldGroup.getField(id); + if (field != null) { + fieldGroup.unbind(field); + panel.removeComponent(field); + } + } } @Override @@ -100,4 +132,5 @@ public void onTestTypeChanged( PathogenTestFormConfig config) { // genotype item update is handled by the registered testTypeListener } + } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CsmDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CsmDiseaseSectionLayout.java index 3bf039d0d57..76a557ed8e5 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CsmDiseaseSectionLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CsmDiseaseSectionLayout.java @@ -17,6 +17,8 @@ *******************************************************************************/ package de.symeda.sormas.ui.samples.diseasesection; +import static de.symeda.sormas.ui.utils.LayoutUtil.fluidRowLocs; + import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -26,11 +28,14 @@ import com.vaadin.ui.CustomLayout; import com.vaadin.v7.data.fieldgroup.FieldGroup; +import com.vaadin.v7.ui.Field; +import com.vaadin.v7.ui.TextField; import de.symeda.sormas.api.Disease; import de.symeda.sormas.api.sample.PathogenTestDto; import de.symeda.sormas.api.sample.PathogenTestResultType; import de.symeda.sormas.ui.utils.FieldHelper; +import de.symeda.sormas.ui.utils.Registration; /** * Disease section for CSM: wires serotype visibility (positive result only). @@ -38,11 +43,17 @@ */ public class CsmDiseaseSectionLayout implements DiseaseSectionLayout { + //@formatter:off + private static final String HTML = fluidRowLocs(PathogenTestDto.SEROTYPE, ""); + //@formatter:on + private static final List FIELD_IDS = Collections.singletonList(PathogenTestDto.SEROTYPE); + private Registration visibilityRegistration; + @Override public String getHtmlLayout() { - return ""; + return HTML; } @Override @@ -52,17 +63,26 @@ public Collection getFieldIds() { @Override public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease, PathogenTestFormConfig config) { + TextField serotype = buildAndAdd(fieldGroup, panel, PathogenTestDto.SEROTYPE, TextField.class); + serotype.setVisible(false); + Map> serotypeVisibilityDependencies = new HashMap<>(); serotypeVisibilityDependencies.put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.CSM)); serotypeVisibilityDependencies.put(PathogenTestDto.TEST_RESULT, Arrays.asList(PathogenTestResultType.POSITIVE)); - FieldHelper.setVisibleWhen(fieldGroup, Arrays.asList(PathogenTestDto.SEROTYPE), serotypeVisibilityDependencies, true); + visibilityRegistration = + FieldHelper.setVisibleWhen(fieldGroup, Arrays.asList(PathogenTestDto.SEROTYPE), serotypeVisibilityDependencies, true); } @Override public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { - // setVisibleWhen listeners are attached to TESTED_DISEASE and TEST_RESULT source fields. - // Those source fields remain in the FieldGroup across section swaps and their - // multi-condition visibility logic is harmless when this disease is not active, - // because the TESTED_DISEASE condition will never match CSM for another section. + if (visibilityRegistration != null) { + visibilityRegistration.remove(); + visibilityRegistration = null; + } + Field field = fieldGroup.getField(PathogenTestDto.SEROTYPE); + if (field != null) { + fieldGroup.unbind(field); + panel.removeComponent(field); + } } } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/DiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/DiseaseSectionLayout.java index 7bc707918e3..40e178874a9 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/DiseaseSectionLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/DiseaseSectionLayout.java @@ -18,14 +18,23 @@ package de.symeda.sormas.ui.samples.diseasesection; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import com.vaadin.server.Sizeable; import com.vaadin.ui.CustomLayout; import com.vaadin.v7.data.fieldgroup.FieldGroup; import com.vaadin.v7.ui.AbstractField; +import com.vaadin.v7.ui.Field; import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.i18n.I18nProperties; +import de.symeda.sormas.api.sample.PathogenTestDto; import de.symeda.sormas.api.sample.PathogenTestResultType; import de.symeda.sormas.api.sample.PathogenTestType; +import de.symeda.sormas.ui.utils.CssStyles; /** * Encapsulates the disease-specific portion of PathogenTestForm. @@ -75,6 +84,37 @@ default void onTestTypeChanged( PathogenTestFormConfig config) { } + /** + * Builds a field via {@link FieldGroup#buildAndBind}, configures it with standard caption/style/width, + * and adds it to the given panel. Shared by all disease section implementations. + */ + default > F buildAndAdd(FieldGroup fieldGroup, CustomLayout panel, String propertyId, Class fieldType) { + F field = fieldGroup.buildAndBind(propertyId, (Object) propertyId, fieldType); + field.setId(propertyId); + field.setCaption(I18nProperties.getPrefixCaption(PathogenTestDto.I18N_PREFIX, propertyId, field.getCaption())); + CssStyles.style(field, CssStyles.CAPTION_ON_TOP); + field.setWidth(100, Sizeable.Unit.PERCENTAGE); + panel.addComponent(field, propertyId); + return field; + } + + /** + * Builds an unmodifiable visibility-condition map without anonymous inner classes. + * Accepts alternating key/value pairs where each value is a List of allowed values. + */ + @SuppressWarnings("unchecked") + static Map> conditionsOf(Object... keyValuePairs) { + if (keyValuePairs.length % 2 != 0) { + throw new IllegalArgumentException("Expected an even number of arguments (key/value pairs)"); + } + Map> map = new HashMap<>(); + for (int i = 0; i < keyValuePairs.length; i += 2) { + map.put(keyValuePairs[i], Collections.unmodifiableList((List) keyValuePairs[i + 1])); + } + + return Collections.unmodifiableMap(map); + } + /** Factory: returns the correct section implementation for the given disease. */ static DiseaseSectionLayout forDisease(Disease disease) { if (disease == null) { diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/ImiDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/ImiDiseaseSectionLayout.java index 254ee3e2b4c..e35a3652960 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/ImiDiseaseSectionLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/ImiDiseaseSectionLayout.java @@ -17,6 +17,8 @@ *******************************************************************************/ package de.symeda.sormas.ui.samples.diseasesection; +import static de.symeda.sormas.ui.utils.LayoutUtil.fluidRowLocs; + import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -27,6 +29,9 @@ import com.vaadin.ui.CustomLayout; import com.vaadin.v7.data.fieldgroup.FieldGroup; import com.vaadin.v7.ui.AbstractField; +import com.vaadin.v7.ui.ComboBox; +import com.vaadin.v7.ui.Field; +import com.vaadin.v7.ui.TextField; import de.symeda.sormas.api.Disease; import de.symeda.sormas.api.FacadeProvider; @@ -38,23 +43,30 @@ import de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers; import de.symeda.sormas.ui.therapy.DrugSusceptibilityForm; import de.symeda.sormas.ui.utils.FieldHelper; +import de.symeda.sormas.ui.utils.Registration; /** * Disease section for INVASIVE_MENINGOCOCCAL_INFECTION (IMI): * wires seroGroupSpecification + seroGroupSpecificationText visibility, * and owns the DrugSusceptibilityForm for IMI. - * Fields live in the common layout; this section owns the visibility registrations. */ public class ImiDiseaseSectionLayout implements DiseaseSectionLayout { + //@formatter:off + private static final String HTML = + fluidRowLocs(PathogenTestDto.SERO_GROUP_SPECIFICATION, PathogenTestDto.SERO_GROUP_SPECIFICATION_TEXT) + + fluidRowLocs(PathogenTestDto.DRUG_SUSCEPTIBILITY); + //@formatter:on + private static final List FIELD_IDS = Collections.unmodifiableList( Arrays.asList(PathogenTestDto.SERO_GROUP_SPECIFICATION, PathogenTestDto.SERO_GROUP_SPECIFICATION_TEXT, PathogenTestDto.DRUG_SUSCEPTIBILITY)); private DrugSusceptibilityForm drugSusceptibilityField; + private Registration visibilityRegistration; @Override public String getHtmlLayout() { - return ""; + return HTML; } @Override @@ -64,6 +76,12 @@ public Collection getFieldIds() { @Override public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease, PathogenTestFormConfig config) { + ComboBox seroGroupSpecification = buildAndAdd(fieldGroup, panel, PathogenTestDto.SERO_GROUP_SPECIFICATION, ComboBox.class); + seroGroupSpecification.setVisible(false); + + TextField seroGroupSpecificationText = buildAndAdd(fieldGroup, panel, PathogenTestDto.SERO_GROUP_SPECIFICATION_TEXT, TextField.class); + seroGroupSpecificationText.setVisible(false); + Map> imiSeroTypingDependencies = new HashMap<>(); imiSeroTypingDependencies.put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.INVASIVE_MENINGOCOCCAL_INFECTION)); imiSeroTypingDependencies.put(PathogenTestDto.TEST_RESULT, Arrays.asList(PathogenTestResultType.POSITIVE)); @@ -74,23 +92,41 @@ public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease diseas PathogenTestType.MULTILOCUS_SEQUENCE_TYPING, PathogenTestType.SLIDE_AGGLUTINATION, PathogenTestType.WHOLE_GENOME_SEQUENCING)); - FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.SERO_GROUP_SPECIFICATION, imiSeroTypingDependencies, true); - FieldHelper.setVisibleWhen( + Registration r1 = FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.SERO_GROUP_SPECIFICATION, imiSeroTypingDependencies, true); + Registration r2 = FieldHelper.setVisibleWhen( fieldGroup, PathogenTestDto.SERO_GROUP_SPECIFICATION_TEXT, PathogenTestDto.SERO_GROUP_SPECIFICATION, SeroGroupSpecification.OTHER, true); + visibilityRegistration = Registration.combine(r1, r2); drugSusceptibilityField = new DrugSusceptibilityForm( FieldVisibilityCheckers.getNoop(), UiFieldAccessCheckers.getDefault(true, FacadeProvider.getConfigFacade().getCountryLocale())); drugSusceptibilityField.setCaption(null); fieldGroup.bind(drugSusceptibilityField, PathogenTestDto.DRUG_SUSCEPTIBILITY); + panel.addComponent(drugSusceptibilityField, PathogenTestDto.DRUG_SUSCEPTIBILITY); } @Override public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { + if (visibilityRegistration != null) { + visibilityRegistration.remove(); + visibilityRegistration = null; + } + + for (String id : FIELD_IDS) { + if (PathogenTestDto.DRUG_SUSCEPTIBILITY.equals(id)) { + continue; + } + Field field = fieldGroup.getField(id); + if (field != null) { + fieldGroup.unbind(field); + panel.removeComponent(field); + } + } + if (drugSusceptibilityField != null) { fieldGroup.unbind(drugSusceptibilityField); panel.removeComponent(drugSusceptibilityField); @@ -116,4 +152,5 @@ public void onTestTypeChanged( testResultField.setValue(PathogenTestResultType.POSITIVE); } } + } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/IpiDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/IpiDiseaseSectionLayout.java index 0bf953e75de..315985c6765 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/IpiDiseaseSectionLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/IpiDiseaseSectionLayout.java @@ -17,6 +17,8 @@ *******************************************************************************/ package de.symeda.sormas.ui.samples.diseasesection; +import static de.symeda.sormas.ui.utils.LayoutUtil.fluidRowLocs; + import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -27,6 +29,9 @@ import com.vaadin.ui.CustomLayout; import com.vaadin.v7.data.fieldgroup.FieldGroup; import com.vaadin.v7.ui.AbstractField; +import com.vaadin.v7.ui.ComboBox; +import com.vaadin.v7.ui.Field; +import com.vaadin.v7.ui.TextField; import de.symeda.sormas.api.Disease; import de.symeda.sormas.api.FacadeProvider; @@ -38,15 +43,22 @@ import de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers; import de.symeda.sormas.ui.therapy.DrugSusceptibilityForm; import de.symeda.sormas.ui.utils.FieldHelper; +import de.symeda.sormas.ui.utils.Registration; /** * Disease section for INVASIVE_PNEUMOCOCCAL_INFECTION (IPI): * wires serotype + serotypingMethod + serotypingMethodText visibility, * and owns the DrugSusceptibilityForm for IPI. - * Fields live in the common layout; this section owns the visibility registrations. */ public class IpiDiseaseSectionLayout implements DiseaseSectionLayout { + //@formatter:off + private static final String HTML = + fluidRowLocs(PathogenTestDto.SEROTYPE, PathogenTestDto.SEROTYPING_METHOD) + + fluidRowLocs(PathogenTestDto.SERO_TYPING_METHOD_TEXT, "") + + fluidRowLocs(PathogenTestDto.DRUG_SUSCEPTIBILITY); + //@formatter:on + private static final List FIELD_IDS = Collections.unmodifiableList( Arrays.asList( PathogenTestDto.SEROTYPE, @@ -55,10 +67,11 @@ public class IpiDiseaseSectionLayout implements DiseaseSectionLayout { PathogenTestDto.DRUG_SUSCEPTIBILITY)); private DrugSusceptibilityForm drugSusceptibilityField; + private Registration visibilityRegistration; @Override public String getHtmlLayout() { - return ""; + return HTML; } @Override @@ -68,12 +81,21 @@ public Collection getFieldIds() { @Override public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease, PathogenTestFormConfig config) { + TextField serotype = buildAndAdd(fieldGroup, panel, PathogenTestDto.SEROTYPE, TextField.class); + serotype.setVisible(false); + + ComboBox serotypingMethod = buildAndAdd(fieldGroup, panel, PathogenTestDto.SEROTYPING_METHOD, ComboBox.class); + serotypingMethod.setVisible(false); + + TextField serotypingMethodText = buildAndAdd(fieldGroup, panel, PathogenTestDto.SERO_TYPING_METHOD_TEXT, TextField.class); + serotypingMethodText.setVisible(false); + // serotype + serotypingMethod visible on SEROGROUPING + POSITIVE Map> serotypeAndMethodDependencies = new HashMap<>(); serotypeAndMethodDependencies.put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.INVASIVE_PNEUMOCOCCAL_INFECTION)); serotypeAndMethodDependencies.put(PathogenTestDto.TEST_TYPE, Arrays.asList(PathogenTestType.SEROGROUPING)); serotypeAndMethodDependencies.put(PathogenTestDto.TEST_RESULT, Arrays.asList(PathogenTestResultType.POSITIVE)); - FieldHelper.setVisibleWhen( + Registration r1 = FieldHelper.setVisibleWhen( fieldGroup, Arrays.asList(PathogenTestDto.SEROTYPE, PathogenTestDto.SEROTYPING_METHOD), serotypeAndMethodDependencies, @@ -90,21 +112,39 @@ public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease diseas PathogenTestType.MULTILOCUS_SEQUENCE_TYPING, PathogenTestType.SEROGROUPING)); serotypeExtendedDependencies.put(PathogenTestDto.TEST_RESULT, Arrays.asList(PathogenTestResultType.POSITIVE)); - FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.SEROTYPE, serotypeExtendedDependencies, true); + Registration r2 = FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.SEROTYPE, serotypeExtendedDependencies, true); // serotypingMethodText visible when method = OTHER - FieldHelper + Registration r3 = FieldHelper .setVisibleWhen(fieldGroup, PathogenTestDto.SERO_TYPING_METHOD_TEXT, PathogenTestDto.SEROTYPING_METHOD, SerotypingMethod.OTHER, true); + visibilityRegistration = Registration.combine(r1, r2, r3); drugSusceptibilityField = new DrugSusceptibilityForm( FieldVisibilityCheckers.getNoop(), UiFieldAccessCheckers.getDefault(true, FacadeProvider.getConfigFacade().getCountryLocale())); drugSusceptibilityField.setCaption(null); fieldGroup.bind(drugSusceptibilityField, PathogenTestDto.DRUG_SUSCEPTIBILITY); + panel.addComponent(drugSusceptibilityField, PathogenTestDto.DRUG_SUSCEPTIBILITY); } @Override public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { + if (visibilityRegistration != null) { + visibilityRegistration.remove(); + visibilityRegistration = null; + } + + for (String id : FIELD_IDS) { + if (PathogenTestDto.DRUG_SUSCEPTIBILITY.equals(id)) { + continue; + } + Field field = fieldGroup.getField(id); + if (field != null) { + fieldGroup.unbind(field); + panel.removeComponent(field); + } + } + if (drugSusceptibilityField != null) { fieldGroup.unbind(drugSusceptibilityField); panel.removeComponent(drugSusceptibilityField); @@ -130,4 +170,5 @@ public void onTestTypeChanged( testResultField.setValue(PathogenTestResultType.POSITIVE); } } + } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/MeaslesDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/MeaslesDiseaseSectionLayout.java index 5e0bf73a33b..c6ebffb5816 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/MeaslesDiseaseSectionLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/MeaslesDiseaseSectionLayout.java @@ -17,6 +17,8 @@ *******************************************************************************/ package de.symeda.sormas.ui.samples.diseasesection; +import static de.symeda.sormas.ui.utils.LayoutUtil.fluidRowLocs; + import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -30,6 +32,7 @@ import com.vaadin.v7.ui.AbstractField; import com.vaadin.v7.ui.ComboBox; import com.vaadin.v7.ui.Field; +import com.vaadin.v7.ui.TextField; import de.symeda.sormas.api.Disease; import de.symeda.sormas.api.sample.GenoTypeResult; @@ -37,21 +40,27 @@ import de.symeda.sormas.api.sample.PathogenTestResultType; import de.symeda.sormas.api.sample.PathogenTestType; import de.symeda.sormas.ui.utils.FieldHelper; +import de.symeda.sormas.ui.utils.Registration; /** * Disease section for MEASLES: wires genotype result + genotype result text visibility. - * Fields live in the common layout; this section owns the visibility registrations. */ public class MeaslesDiseaseSectionLayout implements DiseaseSectionLayout { + //@formatter:off + private static final String HTML = + fluidRowLocs(4, PathogenTestDto.GENOTYPE_RESULT, 6, PathogenTestDto.GENOTYPE_RESULT_TEXT); + //@formatter:on + private static final List FIELD_IDS = Collections.unmodifiableList(Arrays.asList(PathogenTestDto.GENOTYPE_RESULT, PathogenTestDto.GENOTYPE_RESULT_TEXT)); private Property.ValueChangeListener testTypeListener; + private Registration visibilityRegistration; @Override public String getHtmlLayout() { - return ""; + return HTML; } @Override @@ -61,12 +70,24 @@ public Collection getFieldIds() { @Override public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease disease, PathogenTestFormConfig config) { + ComboBox genoTypeResult = buildAndAdd(fieldGroup, panel, PathogenTestDto.GENOTYPE_RESULT, ComboBox.class); + genoTypeResult.setVisible(false); + + TextField genoTypeResultText = buildAndAdd(fieldGroup, panel, PathogenTestDto.GENOTYPE_RESULT_TEXT, TextField.class); + genoTypeResultText.setVisible(false); + Map> genoTypingDependencies = new HashMap<>(); genoTypingDependencies.put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.MEASLES, Disease.CRYPTOSPORIDIOSIS)); genoTypingDependencies.put(PathogenTestDto.TEST_TYPE, Arrays.asList(PathogenTestType.GENOTYPING)); genoTypingDependencies.put(PathogenTestDto.TEST_RESULT, Arrays.asList(PathogenTestResultType.POSITIVE)); - FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.GENOTYPE_RESULT, genoTypingDependencies, true); - FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.GENOTYPE_RESULT_TEXT, PathogenTestDto.GENOTYPE_RESULT, GenoTypeResult.OTHER, true); + Registration r1 = FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.GENOTYPE_RESULT, genoTypingDependencies, true); + Map> genotypeTextDependencies = new HashMap<>(genoTypingDependencies); + genotypeTextDependencies.put(PathogenTestDto.GENOTYPE_RESULT, Arrays.asList(GenoTypeResult.OTHER)); + Registration r2 = FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.GENOTYPE_RESULT_TEXT, genotypeTextDependencies, true); + visibilityRegistration = Registration.combine(r1, r2); + + // Populate genotype items for the current disease + FieldHelper.updateItems(disease, genoTypeResult, GenoTypeResult.class); // Update genotype items when test type changes testTypeListener = e -> { @@ -84,6 +105,10 @@ public void bindFields(FieldGroup fieldGroup, CustomLayout panel, Disease diseas @Override public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { + if (visibilityRegistration != null) { + visibilityRegistration.remove(); + visibilityRegistration = null; + } if (testTypeListener != null) { Field testTypeField = fieldGroup.getField(PathogenTestDto.TEST_TYPE); if (testTypeField != null) { @@ -91,6 +116,14 @@ public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { } testTypeListener = null; } + + for (String id : FIELD_IDS) { + Field field = fieldGroup.getField(id); + if (field != null) { + fieldGroup.unbind(field); + panel.removeComponent(field); + } + } } @Override @@ -101,4 +134,5 @@ public void onTestTypeChanged( PathogenTestFormConfig config) { // genotype item update is handled by the registered testTypeListener } + } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/TubeFieldHandler.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/TubeFieldHandler.java index 477e89a25ff..4fac058b865 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/TubeFieldHandler.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/TubeFieldHandler.java @@ -22,12 +22,14 @@ import com.vaadin.ui.CustomLayout; import com.vaadin.v7.data.Property; import com.vaadin.v7.data.fieldgroup.FieldGroup; +import com.vaadin.v7.data.util.converter.Converter; import com.vaadin.v7.ui.Field; import com.vaadin.v7.ui.TextField; import de.symeda.sormas.api.i18n.I18nProperties; import de.symeda.sormas.api.i18n.Validations; import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.ui.utils.CssStyles; import de.symeda.sormas.ui.utils.NullableOptionGroup; /** @@ -65,17 +67,23 @@ public void detachTubeFields() { private void addTubePair(String numericId, String gt10Id) { TextField numericField = fieldGroup.buildAndBind(numericId, (Object) numericId, TextField.class); numericField.setId(numericId); + numericField.setCaption(I18nProperties.getPrefixCaption(PathogenTestDto.I18N_PREFIX, numericId, numericField.getCaption())); numericField.setConversionError(I18nProperties.getValidationError(Validations.onlyNumbersAllowed, numericField.getCaption())); + CssStyles.style(numericField, CssStyles.CAPTION_ON_TOP); + numericField.setWidth("100%"); numericField.setVisible(false); panel.addComponent(numericField, numericId); NullableOptionGroup gt10Field = fieldGroup.buildAndBind(gt10Id, (Object) gt10Id, NullableOptionGroup.class); gt10Field.setId(gt10Id); + gt10Field.setCaption(I18nProperties.getPrefixCaption(PathogenTestDto.I18N_PREFIX, gt10Id, gt10Field.getCaption())); + CssStyles.style(gt10Field, CssStyles.CAPTION_ON_TOP); + gt10Field.setWidth("100%"); gt10Field.setVisible(false); panel.addComponent(gt10Field, gt10Id); // numeric → auto-check GT10 when value > 10 - numericField.addValueChangeListener(e -> handleNumericChange((String) e.getProperty().getValue(), numericId, gt10Id)); + numericField.addValueChangeListener(e -> handleNumericChange(numericId, gt10Id)); // GT10 checkbox → clear numeric if value contradicts the checkbox gt10Field.addValueChangeListener(e -> handleGt10Change(e, numericId)); @@ -94,21 +102,21 @@ private void detachTubePair(String numericId, String gt10Id) { } } - private void handleNumericChange(String val, String numericId, String gt10Id) { + private void handleNumericChange(String numericId, String gt10Id) { NullableOptionGroup gt10 = (NullableOptionGroup) fieldGroup.getField(gt10Id); if (gt10 == null) { return; } - if (val == null) { - gt10.select(false); + TextField numericField = (TextField) fieldGroup.getField(numericId); + if (numericField == null) { return; } - try { - gt10.select(Float.parseFloat(val) > 10); - } catch (NumberFormatException e) { - fieldGroup.getField(numericId).clear(); + Float converted = getConvertedFloat(numericField); + if (converted == null) { gt10.select(false); + return; } + gt10.select(converted > 10); } private void handleGt10Change(Property.ValueChangeEvent e, String numericId) { @@ -116,24 +124,28 @@ private void handleGt10Change(Property.ValueChangeEvent e, String numericId) { ? ((Collection) e.getProperty().getValue()).stream().findFirst().orElse(null) : e.getProperty().getValue(); - Field numericField = fieldGroup.getField(numericId); + TextField numericField = (TextField) fieldGroup.getField(numericId); if (numericField == null) { return; } - String numVal = (String) numericField.getValue(); - if (raw == null || numVal == null) { + Float converted = getConvertedFloat(numericField); + if (raw == null || converted == null) { return; } boolean checked = Boolean.TRUE.equals(raw); - try { - float f = Float.parseFloat(numVal); - if (checked && f <= 10) { - numericField.clear(); - } else if (!checked && f > 10) { - numericField.clear(); - } - } catch (NumberFormatException ex) { + if (checked && converted <= 10) { numericField.clear(); + } else if (!checked && converted > 10) { + numericField.clear(); + } + } + + /** Locale-aware conversion; returns null for empty or unparseable input (e.g. trailing comma). */ + private static Float getConvertedFloat(TextField field) { + try { + return (Float) field.getConvertedValue(); + } catch (Converter.ConversionException e) { + return null; } } } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/TuberculosisDiseaseSectionLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/TuberculosisDiseaseSectionLayout.java index c4af635cdd6..709745b25e0 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/TuberculosisDiseaseSectionLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/TuberculosisDiseaseSectionLayout.java @@ -21,8 +21,6 @@ import java.util.Arrays; import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -46,6 +44,7 @@ import de.symeda.sormas.ui.therapy.DrugSusceptibilityForm; import de.symeda.sormas.ui.utils.FieldHelper; import de.symeda.sormas.ui.utils.NullableOptionGroup; +import de.symeda.sormas.ui.utils.Registration; /** * Disease-specific section for TUBERCULOSIS and LATENT_TUBERCULOSIS. @@ -84,39 +83,33 @@ public class TuberculosisDiseaseSectionLayout implements DiseaseSectionLayout { PathogenTestDto.TUBE_MITOGENE, PathogenTestDto.TUBE_MITOGENE_GT10); - public static final Map> RIFAMPICIN_RESISTANT_VISIBILITY_CONDITIONS = Collections.unmodifiableMap(new HashMap<>() { - - { - put(PathogenTestDto.TESTED_DISEASE, Collections.unmodifiableList(Arrays.asList(Disease.LATENT_TUBERCULOSIS, Disease.TUBERCULOSIS))); - put(PathogenTestDto.TEST_TYPE, Collections.unmodifiableList(Arrays.asList(PathogenTestType.PCR_RT_PCR))); - put(PathogenTestDto.TEST_RESULT, Collections.unmodifiableList(Arrays.asList(PathogenTestResultType.POSITIVE))); - } - }); - - public static final Map> TEST_SCALE_VISIBILITY_CONDITIONS = Collections.unmodifiableMap(new HashMap<>() { - - { - put(PathogenTestDto.TESTED_DISEASE, Collections.unmodifiableList(Arrays.asList(Disease.LATENT_TUBERCULOSIS, Disease.TUBERCULOSIS))); - put(PathogenTestDto.TEST_TYPE, Collections.unmodifiableList(Arrays.asList(PathogenTestType.MICROSCOPY))); - } - }); - - public static final Map> STRAIN_CALL_STATUS_VISIBILITY_CONDITIONS = Collections.unmodifiableMap(new HashMap<>() { - - { - put(PathogenTestDto.TESTED_DISEASE, Collections.unmodifiableList(Arrays.asList(Disease.LATENT_TUBERCULOSIS, Disease.TUBERCULOSIS))); - put(PathogenTestDto.TEST_TYPE, Collections.unmodifiableList(Arrays.asList(PathogenTestType.BEIJINGGENOTYPING))); - } - }); - - public static final Map> SPECIE_VISIBILITY_CONDITIONS = Collections.unmodifiableMap(new HashMap<>() { - - { - put(PathogenTestDto.TESTED_DISEASE, Collections.unmodifiableList(Arrays.asList(Disease.LATENT_TUBERCULOSIS, Disease.TUBERCULOSIS))); - put(PathogenTestDto.TEST_TYPE, Collections.unmodifiableList(Arrays.asList(PathogenTestType.SPOLIGOTYPING))); - put(PathogenTestDto.TEST_RESULT, Collections.unmodifiableList(Arrays.asList(PathogenTestResultType.POSITIVE))); - } - }); + public static final Map> RIFAMPICIN_RESISTANT_VISIBILITY_CONDITIONS = DiseaseSectionLayout.conditionsOf( + PathogenTestDto.TESTED_DISEASE, + Arrays.asList(Disease.LATENT_TUBERCULOSIS, Disease.TUBERCULOSIS), + PathogenTestDto.TEST_TYPE, + Arrays.asList(PathogenTestType.PCR_RT_PCR), + PathogenTestDto.TEST_RESULT, + Arrays.asList(PathogenTestResultType.POSITIVE)); + + public static final Map> TEST_SCALE_VISIBILITY_CONDITIONS = DiseaseSectionLayout.conditionsOf( + PathogenTestDto.TESTED_DISEASE, + Arrays.asList(Disease.LATENT_TUBERCULOSIS, Disease.TUBERCULOSIS), + PathogenTestDto.TEST_TYPE, + Arrays.asList(PathogenTestType.MICROSCOPY)); + + public static final Map> STRAIN_CALL_STATUS_VISIBILITY_CONDITIONS = DiseaseSectionLayout.conditionsOf( + PathogenTestDto.TESTED_DISEASE, + Arrays.asList(Disease.LATENT_TUBERCULOSIS, Disease.TUBERCULOSIS), + PathogenTestDto.TEST_TYPE, + Arrays.asList(PathogenTestType.BEIJINGGENOTYPING)); + + public static final Map> SPECIE_VISIBILITY_CONDITIONS = DiseaseSectionLayout.conditionsOf( + PathogenTestDto.TESTED_DISEASE, + Arrays.asList(Disease.LATENT_TUBERCULOSIS, Disease.TUBERCULOSIS), + PathogenTestDto.TEST_TYPE, + Arrays.asList(PathogenTestType.SPOLIGOTYPING), + PathogenTestDto.TEST_RESULT, + Arrays.asList(PathogenTestResultType.POSITIVE)); // Held for onTestTypeChanged and unbindFields private DrugSusceptibilityForm drugSusceptibilityField; @@ -128,6 +121,7 @@ public class TuberculosisDiseaseSectionLayout implements DiseaseSectionLayout { private Property.ValueChangeListener testTypeListener; private Property.ValueChangeListener testResultListener; private Property.ValueChangeListener diseaseListener; + private Registration visibilityRegistration; @Override public String getHtmlLayout() { @@ -193,9 +187,15 @@ private void bindLuxembourgVisibility(FieldGroup fieldGroup, ComboBox strainCall testResultListener = e -> setTubeFieldsVisible(fieldGroup, (PathogenTestType) fieldGroup.getField(PathogenTestDto.TEST_TYPE).getValue(), isLuxembourg); diseaseListener = e -> { - setTubeFieldsVisible(fieldGroup, (PathogenTestType) fieldGroup.getField(PathogenTestDto.TEST_TYPE).getValue(), isLuxembourg); - + PathogenTestType currentTestType = (PathogenTestType) fieldGroup.getField(PathogenTestDto.TEST_TYPE).getValue(); Disease newDisease = (Disease) e.getProperty().getValue(); + + setTubeFieldsVisible(fieldGroup, currentTestType, isLuxembourg); + + if (drugSusceptibilityField != null) { + drugSusceptibilityField.updateFieldsVisibility(newDisease, currentTestType); + } + FieldHelper.updateItems( strainCallStatus, Arrays.asList(PathogenStrainCallStatus.values()), @@ -208,15 +208,22 @@ private void bindLuxembourgVisibility(FieldGroup fieldGroup, ComboBox strainCall fieldGroup.getField(PathogenTestDto.TESTED_DISEASE).addValueChangeListener(diseaseListener); // Wire standard multi-source visibility conditions - FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.RIFAMPICIN_RESISTANT, RIFAMPICIN_RESISTANT_VISIBILITY_CONDITIONS, true); - FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.TEST_SCALE, TEST_SCALE_VISIBILITY_CONDITIONS, true); - FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.STRAIN_CALL_STATUS, STRAIN_CALL_STATUS_VISIBILITY_CONDITIONS, true); - FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.SPECIE, SPECIE_VISIBILITY_CONDITIONS, true); - - Map> miruCode = new HashMap<>(); - miruCode.put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.TUBERCULOSIS, Disease.LATENT_TUBERCULOSIS)); - miruCode.put(PathogenTestDto.TEST_TYPE, Arrays.asList(PathogenTestType.MIRU_PATTERN_CODE)); - FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.PATTERN_PROFILE, miruCode, true); + Registration r1 = + FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.RIFAMPICIN_RESISTANT, RIFAMPICIN_RESISTANT_VISIBILITY_CONDITIONS, true); + Registration r2 = FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.TEST_SCALE, TEST_SCALE_VISIBILITY_CONDITIONS, true); + Registration r3 = FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.STRAIN_CALL_STATUS, STRAIN_CALL_STATUS_VISIBILITY_CONDITIONS, true); + Registration r4 = FieldHelper.setVisibleWhen(fieldGroup, PathogenTestDto.SPECIE, SPECIE_VISIBILITY_CONDITIONS, true); + + Registration r5 = FieldHelper.setVisibleWhen( + fieldGroup, + PathogenTestDto.PATTERN_PROFILE, + DiseaseSectionLayout.conditionsOf( + PathogenTestDto.TESTED_DISEASE, + Arrays.asList(Disease.TUBERCULOSIS, Disease.LATENT_TUBERCULOSIS), + PathogenTestDto.TEST_TYPE, + Arrays.asList(PathogenTestType.MIRU_PATTERN_CODE)), + true); + visibilityRegistration = Registration.combine(r1, r2, r3, r4, r5); FieldHelper.updateItems( strainCallStatus, @@ -246,6 +253,11 @@ private void setTubeFieldsVisible(FieldGroup fieldGroup, PathogenTestType testTy @Override public void unbindFields(FieldGroup fieldGroup, CustomLayout panel) { + if (visibilityRegistration != null) { + visibilityRegistration.remove(); + visibilityRegistration = null; + } + // Remove named listeners from shared source fields before unbinding section fields if (testTypeListener != null) { Field f = fieldGroup.getField(PathogenTestDto.TEST_TYPE); @@ -319,10 +331,4 @@ public void onTestTypeChanged( } } - private > F buildAndAdd(FieldGroup fieldGroup, CustomLayout panel, String propertyId, Class fieldType) { - F field = fieldGroup.buildAndBind(propertyId, (Object) propertyId, fieldType); - field.setId(propertyId); - panel.addComponent(field, propertyId); - return field; - } } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/AbstractEditForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/AbstractEditForm.java index d65c71f8129..ca681ed3918 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/AbstractEditForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/AbstractEditForm.java @@ -557,11 +557,15 @@ protected void addToVisibleAllowedFields(Field field) { } protected void removeFromVisibleAllowedFields(Field field) { - visibleAllowedFields.remove(field); + visibleAllowedFields.removeIf(f -> f == field); + } + + protected void addToEditableAllowedFields(Field field) { + editableAllowedFields.add(field); } protected void removeFromEditableAllowedFields(Field field) { - editableAllowedFields.remove(field); + editableAllowedFields.removeIf(f -> f == field); } /** diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/AbstractForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/AbstractForm.java index 0ebe4cb9956..08afa06ebcf 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/AbstractForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/AbstractForm.java @@ -563,17 +563,24 @@ protected void configureField(Field field) { * Iterates a snapshot of the field collection so that value-change listeners triggered * during discard (e.g. disease-section swap that binds/unbinds fields) cannot cause a * ConcurrentModificationException on the underlying LinkedHashMap. - * Per-field discard failures are swallowed to match the behaviour of the super implementation. + * All fields are attempted even if one fails; the first failure is rethrown after all discards complete. */ @Override public void discard() { + Exception firstFailure = null; for (Field f : new ArrayList<>(getFields())) { try { f.discard(); } catch (Exception e) { - // intentional: matches FieldGroup.discard() original behaviour + if (firstFailure == null) { + firstFailure = e; + } + logger.warn("Discard failed for field {}", f.getId(), e); } } + if (firstFailure != null) { + throw (RuntimeException) firstFailure; + } } } } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/FieldHelper.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/FieldHelper.java index 6f09e86e04b..753ac65a0ee 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/FieldHelper.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/FieldHelper.java @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -35,6 +36,7 @@ import com.vaadin.ui.Component; import com.vaadin.ui.HasComponents; import com.vaadin.v7.data.Item; +import com.vaadin.v7.data.Property; import com.vaadin.v7.data.fieldgroup.DefaultFieldGroupFieldFactory; import com.vaadin.v7.data.fieldgroup.FieldGroup; import com.vaadin.v7.ui.AbstractField; @@ -145,38 +147,38 @@ public static void setVisibleWithCheckersWhen( } } - public static void setVisibleWhen( + public static Registration setVisibleWhen( FieldGroup fieldGroup, String targetPropertyId, Object sourcePropertyId, Object sourceValue, boolean clearOnHidden) { - setVisibleWhen(fieldGroup, targetPropertyId, sourcePropertyId, Arrays.asList(sourceValue), clearOnHidden); + return setVisibleWhen(fieldGroup, targetPropertyId, sourcePropertyId, Arrays.asList(sourceValue), clearOnHidden); } - public static void setVisibleWhen( + public static Registration setVisibleWhen( FieldGroup fieldGroup, String targetPropertyId, Object sourcePropertyId, List sourceValues, boolean clearOnHidden) { - setVisibleWhen(fieldGroup, Arrays.asList(targetPropertyId), sourcePropertyId, sourceValues, clearOnHidden); + return setVisibleWhen(fieldGroup, Arrays.asList(targetPropertyId), sourcePropertyId, sourceValues, clearOnHidden); } - public static void setVisibleWhen( + public static Registration setVisibleWhen( FieldGroup fieldGroup, List targetPropertyIds, Object sourcePropertyId, Object sourceValue, boolean clearOnHidden) { - setVisibleWhen(fieldGroup, targetPropertyIds, sourcePropertyId, Arrays.asList(sourceValue), clearOnHidden); + return setVisibleWhen(fieldGroup, targetPropertyIds, sourcePropertyId, Arrays.asList(sourceValue), clearOnHidden); } @SuppressWarnings("rawtypes") - public static void setVisibleWhen( + public static Registration setVisibleWhen( final FieldGroup fieldGroup, List targetPropertyIds, Object sourcePropertyId, @@ -185,22 +187,22 @@ public static void setVisibleWhen( Field sourceField = fieldGroup.getField(sourcePropertyId); - setVisibleWhen(fieldGroup, targetPropertyIds, sourceField, sourceValues, clearOnHidden); + return setVisibleWhen(fieldGroup, targetPropertyIds, sourceField, sourceValues, clearOnHidden); } @SuppressWarnings("rawtypes") - public static void setVisibleWhen( + public static Registration setVisibleWhen( FieldGroup fieldGroup, String targetPropertyId, Field sourceField, List sourceValues, boolean clearOnHidden) { - setVisibleWhen(fieldGroup, Arrays.asList(targetPropertyId), sourceField, sourceValues, clearOnHidden); + return setVisibleWhen(fieldGroup, Arrays.asList(targetPropertyId), sourceField, sourceValues, clearOnHidden); } @SuppressWarnings("rawtypes") - public static void setVisibleWhen( + public static Registration setVisibleWhen( final FieldGroup fieldGroup, List targetPropertyIds, Field sourceField, @@ -209,12 +211,12 @@ public static void setVisibleWhen( final List> targetFields = targetPropertyIds.stream().map(id -> fieldGroup.getField(id)).collect(Collectors.toList()); - setVisibleWhen(sourceField, targetFields, sourceValues, clearOnHidden); + return setVisibleWhen(sourceField, targetFields, sourceValues, clearOnHidden); } @SuppressWarnings("rawtypes") - public static void setVisibleWhen(Field sourceField, List> targetFields, List sourceValues, boolean clearOnHidden) { - setVisibleWhen(sourceField, targetFields, field -> sourceValues.contains(getNullableSourceFieldValue(field)), clearOnHidden); + public static Registration setVisibleWhen(Field sourceField, List> targetFields, List sourceValues, boolean clearOnHidden) { + return setVisibleWhen(sourceField, targetFields, field -> sourceValues.contains(getNullableSourceFieldValue(field)), clearOnHidden); } @SuppressWarnings("rawtypes") @@ -241,12 +243,12 @@ public static void setVisibleWhenSourceNotNull( } @SuppressWarnings("rawtypes") - public static void setVisibleWhenSourceNotNull(Field sourceField, List> targetFields, boolean clearOnHidden) { - setVisibleWhen(sourceField, targetFields, field -> getNullableSourceFieldValue(field) != null, clearOnHidden); + public static Registration setVisibleWhenSourceNotNull(Field sourceField, List> targetFields, boolean clearOnHidden) { + return setVisibleWhen(sourceField, targetFields, field -> getNullableSourceFieldValue(field) != null, clearOnHidden); } @SuppressWarnings("rawtypes") - public static void setVisibleWhen( + public static Registration setVisibleWhen( Field sourceField, List> targetFields, Function isVisibleFunction, @@ -268,7 +270,7 @@ public static void setVisibleWhen( }); } - sourceField.addValueChangeListener(event -> { + Property.ValueChangeListener listener = event -> { boolean visible = isVisibleFunction.apply((Field) event.getProperty()); targetFields.forEach(targetField -> { targetField.setVisible(visible); @@ -276,8 +278,12 @@ public static void setVisibleWhen( targetField.clear(); } }); - }); + }; + sourceField.addValueChangeListener(listener); + return () -> sourceField.removeValueChangeListener(listener); } + return () -> { + }; } @SuppressWarnings("rawtypes") @@ -313,7 +319,7 @@ public static void setCaptionWhen(Field sourceField, Field targetField, Ob } } - public static void setVisibleWhen( + public static Registration setVisibleWhen( final FieldGroup fieldGroup, List targetPropertyIds, Map> sourcePropertyIdsAndValues, @@ -321,19 +327,36 @@ public static void setVisibleWhen( onValueChangedSetVisible(fieldGroup, targetPropertyIds, sourcePropertyIdsAndValues, clearOnHidden); + // Map from source property ID to the listener added to it (only for fields present in the fieldGroup) + Map listenerMap = new HashMap<>(); sourcePropertyIdsAndValues.forEach((sourcePropertyId, sourceValues) -> { - fieldGroup.getField(sourcePropertyId) - .addValueChangeListener(event -> onValueChangedSetVisible(fieldGroup, targetPropertyIds, sourcePropertyIdsAndValues, clearOnHidden)); + @SuppressWarnings("rawtypes") + Field sourceField = fieldGroup.getField(sourcePropertyId); + if (sourceField == null) { + return; // source field not yet bound — skip listener registration + } + Property.ValueChangeListener listener = + event -> onValueChangedSetVisible(fieldGroup, targetPropertyIds, sourcePropertyIdsAndValues, clearOnHidden); + sourceField.addValueChangeListener(listener); + listenerMap.put(sourcePropertyId, listener); + }); + + return () -> listenerMap.forEach((sourcePropertyId, listener) -> { + @SuppressWarnings("rawtypes") + Field sourceField = fieldGroup.getField(sourcePropertyId); + if (sourceField != null) { + sourceField.removeValueChangeListener(listener); + } }); } - public static void setVisibleWhen( + public static Registration setVisibleWhen( final FieldGroup fieldGroup, String targetPropertyId, Map> sourcePropertyIdsAndValues, final boolean clearOnHidden) { - setVisibleWhen(fieldGroup, Arrays.asList(targetPropertyId), sourcePropertyIdsAndValues, clearOnHidden); + return setVisibleWhen(fieldGroup, Arrays.asList(targetPropertyId), sourcePropertyIdsAndValues, clearOnHidden); } public static void setVisibleWhen(final Field targetField, Map> sourceFieldsAndValues, final boolean clearOnHidden) { @@ -355,7 +378,9 @@ private static void onValueChangedSetVisible( true }; sourcePropertyIdsAndValues.forEach((sourcePropertyId, sourceValues) -> { - if (!sourceValues.contains(fieldGroup.getField(sourcePropertyId).getValue())) + @SuppressWarnings("rawtypes") + Field sourceField = fieldGroup.getField(sourcePropertyId); + if (sourceField == null || !sourceValues.contains(sourceField.getValue())) visibleArray[0] = false; }); @@ -364,7 +389,8 @@ private static void onValueChangedSetVisible( for (Object targetPropertyId : targetPropertyIds) { @SuppressWarnings("rawtypes") Field targetField = fieldGroup.getField(targetPropertyId); - if (targetField == null) continue; // field was unbound (e.g. disease section swap) + if (targetField == null) + continue; // defensive: field may have been unbound by a caller that did not remove its Registration targetField.setVisible(visible); if (!visible && clearOnHidden && targetField.getValue() != null) { targetField.clear(); @@ -901,7 +927,8 @@ private static void onValueChangedSetRequired( true }; sourcePropertyIdsAndValues.forEach((sourcePropertyId, sourceValues) -> { - if (!sourceValues.contains(fieldGroup.getField(sourcePropertyId).getValue())) + Field sourceField = fieldGroup.getField(sourcePropertyId); + if (sourceField == null || !sourceValues.contains(sourceField.getValue())) requiredArray[0] = false; }); @@ -1033,7 +1060,8 @@ private static void onValueChangedSetValue( true }; sourcePropertyIdsAndValues.forEach((sourcePropertyId, sourceValues) -> { - if (!sourceValues.contains(fieldGroup.getField(sourcePropertyId).getValue())) + Field sourceField = fieldGroup.getField(sourcePropertyId); + if (sourceField == null || !sourceValues.contains(sourceField.getValue())) shouldSetValueArray[0] = false; }); @@ -1104,6 +1132,10 @@ private static void onValueChangedSetReadOnly( sourcePropertyIdsAndValues.forEach((sourcePropertyId, sourceValues) -> { Field sourceField = fieldGroup.getField(sourcePropertyId); + if (sourceField == null) { + readOnlyArray[0] = false; + return; + } Object sourceValue = getNullableSourceFieldValue(sourceField); boolean matches; diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/Registration.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/Registration.java new file mode 100644 index 00000000000..a7a92ec1d9a --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/Registration.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2018 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * + * 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.utils; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.List; + +/** + * Handle returned by FieldHelper listener-registration methods. + * Call {@link #remove()} to deregister all listeners that were added during that call. + */ +@FunctionalInterface +public interface Registration extends Serializable { + + void remove(); + + static Registration combine(Registration... registrations) { + List list = Arrays.asList(registrations); + return () -> list.forEach(Registration::remove); + } +} From 1c55e9989eda411716b8c4273e8f5645c4df6a48 Mon Sep 17 00:00:00 2001 From: Harold Asiimwe Date: Fri, 13 Mar 2026 14:59:07 +0300 Subject: [PATCH 10/30] Fixes issue where CT/CQ fields were not being cleared on swaping a disease --- .../sormas/ui/samples/PathogenTestForm.java | 41 +++++++------------ 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java index 6c6644f6a80..7cec6333d6b 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java @@ -89,6 +89,16 @@ public class PathogenTestForm extends AbstractEditForm { private static final String DISEASE_SECTION_LOC = "diseaseSectionLoc"; + private static final String[] CT_CQ_FIELD_IDS = { + PathogenTestDto.CQ_VALUE, + PathogenTestDto.CT_VALUE_E, + PathogenTestDto.CT_VALUE_N, + PathogenTestDto.CT_VALUE_RDRP, + PathogenTestDto.CT_VALUE_S, + PathogenTestDto.CT_VALUE_ORF_1, + PathogenTestDto.CT_VALUE_RDRP_S + }; + //@formatter:off private static final String HTML_LAYOUT = loc(PATHOGEN_TEST_HEADING_LOC) + @@ -452,15 +462,7 @@ private void addCtValueFields() { FieldConfiguration.withConversionError(PathogenTestDto.CT_VALUE_ORF_1, Validations.onlyNumbersAllowed), FieldConfiguration.withConversionError(PathogenTestDto.CT_VALUE_RDRP_S, Validations.onlyNumbersAllowed)); - setVisibleClear( - false, - PathogenTestDto.CQ_VALUE, - PathogenTestDto.CT_VALUE_E, - PathogenTestDto.CT_VALUE_N, - PathogenTestDto.CT_VALUE_RDRP, - PathogenTestDto.CT_VALUE_S, - PathogenTestDto.CT_VALUE_ORF_1, - PathogenTestDto.CT_VALUE_RDRP_S); + setVisibleClear(false, CT_CQ_FIELD_IDS); } private void addPrescriberFields() { @@ -587,29 +589,14 @@ private void bindValueChangeListeners() { } if (diseaseField.getValue() == null || !List.of(Disease.TUBERCULOSIS).contains((Disease) diseaseField.getValue())) { - setVisibleClear( - PathogenTestType.PCR_RT_PCR == testType, - PathogenTestDto.CQ_VALUE, - PathogenTestDto.CT_VALUE_E, - PathogenTestDto.CT_VALUE_N, - PathogenTestDto.CT_VALUE_RDRP, - PathogenTestDto.CT_VALUE_S, - PathogenTestDto.CT_VALUE_ORF_1, - PathogenTestDto.CT_VALUE_RDRP_S); + setVisibleClear(PathogenTestType.PCR_RT_PCR == testType, CT_CQ_FIELD_IDS); } else { - setVisibleClear( - false, - PathogenTestDto.CQ_VALUE, - PathogenTestDto.CT_VALUE_E, - PathogenTestDto.CT_VALUE_N, - PathogenTestDto.CT_VALUE_RDRP, - PathogenTestDto.CT_VALUE_S, - PathogenTestDto.CT_VALUE_ORF_1, - PathogenTestDto.CT_VALUE_RDRP_S); + setVisibleClear(false, CT_CQ_FIELD_IDS); } } else { testResultField.clear(); testResultField.setEnabled(true); + setVisibleClear(false, CT_CQ_FIELD_IDS); } if (RESULT_FIELD_DECISION_MAP.containsKey(disease) && RESULT_FIELD_DECISION_MAP.get(disease).contains(testType)) { From 949877755d372fc3faa8d74846581bbee5aa8590 Mon Sep 17 00:00:00 2001 From: Harold Asiimwe Date: Fri, 13 Mar 2026 15:16:04 +0300 Subject: [PATCH 11/30] Fixes issue where Listeners added in addTubePair are never removed in detachTubePair. --- .../diseasesection/TubeFieldHandler.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/TubeFieldHandler.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/TubeFieldHandler.java index 4fac058b865..2ad4913a4d2 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/TubeFieldHandler.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/TubeFieldHandler.java @@ -18,6 +18,8 @@ package de.symeda.sormas.ui.samples.diseasesection; import java.util.Collection; +import java.util.HashMap; +import java.util.Map; import com.vaadin.ui.CustomLayout; import com.vaadin.v7.data.Property; @@ -42,6 +44,8 @@ public class TubeFieldHandler { private final FieldGroup fieldGroup; private final CustomLayout panel; + private final Map numericListeners = new HashMap<>(); + private final Map gt10Listeners = new HashMap<>(); public TubeFieldHandler(FieldGroup fieldGroup, CustomLayout panel) { this.fieldGroup = fieldGroup; @@ -82,21 +86,31 @@ private void addTubePair(String numericId, String gt10Id) { gt10Field.setVisible(false); panel.addComponent(gt10Field, gt10Id); - // numeric → auto-check GT10 when value > 10 - numericField.addValueChangeListener(e -> handleNumericChange(numericId, gt10Id)); + Property.ValueChangeListener numericListener = e -> handleNumericChange(numericId, gt10Id); + numericField.addValueChangeListener(numericListener); + numericListeners.put(numericId, numericListener); - // GT10 checkbox → clear numeric if value contradicts the checkbox - gt10Field.addValueChangeListener(e -> handleGt10Change(e, numericId)); + Property.ValueChangeListener gt10Listener = e -> handleGt10Change(e, numericId); + gt10Field.addValueChangeListener(gt10Listener); + gt10Listeners.put(gt10Id, gt10Listener); } private void detachTubePair(String numericId, String gt10Id) { Field numericField = fieldGroup.getField(numericId); if (numericField != null) { + Property.ValueChangeListener listener = numericListeners.remove(numericId); + if (listener != null) { + numericField.removeValueChangeListener(listener); + } fieldGroup.unbind(numericField); panel.removeComponent(numericField); } Field gt10Field = fieldGroup.getField(gt10Id); if (gt10Field != null) { + Property.ValueChangeListener listener = gt10Listeners.remove(gt10Id); + if (listener != null) { + gt10Field.removeValueChangeListener(listener); + } fieldGroup.unbind(gt10Field); panel.removeComponent(gt10Field); } From e58a2698af5e847eacfc3f8f7e791bcbc866d62d Mon Sep 17 00:00:00 2001 From: Harold Asiimwe Date: Mon, 16 Mar 2026 13:48:26 +0300 Subject: [PATCH 12/30] Resolve target fields eagerly in FieldHelper.setVisibleWhen to eliminate defensive null check --- .../symeda/sormas/ui/utils/FieldHelper.java | 34 +++++++------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/FieldHelper.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/FieldHelper.java index 753ac65a0ee..6f3fce5cc9e 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/FieldHelper.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/FieldHelper.java @@ -319,30 +319,33 @@ public static void setCaptionWhen(Field sourceField, Field targetField, Ob } } + @SuppressWarnings("rawtypes") public static Registration setVisibleWhen( final FieldGroup fieldGroup, List targetPropertyIds, Map> sourcePropertyIdsAndValues, final boolean clearOnHidden) { - onValueChangedSetVisible(fieldGroup, targetPropertyIds, sourcePropertyIdsAndValues, clearOnHidden); + // Resolve target fields eagerly so the listener holds direct references + List targetFields = + targetPropertyIds.stream().map(id -> fieldGroup.getField(id)).filter(Objects::nonNull).collect(Collectors.toList()); + + onValueChangedSetVisible(fieldGroup, targetFields, sourcePropertyIdsAndValues, clearOnHidden); // Map from source property ID to the listener added to it (only for fields present in the fieldGroup) Map listenerMap = new HashMap<>(); sourcePropertyIdsAndValues.forEach((sourcePropertyId, sourceValues) -> { - @SuppressWarnings("rawtypes") Field sourceField = fieldGroup.getField(sourcePropertyId); if (sourceField == null) { return; // source field not yet bound — skip listener registration } Property.ValueChangeListener listener = - event -> onValueChangedSetVisible(fieldGroup, targetPropertyIds, sourcePropertyIdsAndValues, clearOnHidden); + event -> onValueChangedSetVisible(fieldGroup, targetFields, sourcePropertyIdsAndValues, clearOnHidden); sourceField.addValueChangeListener(listener); listenerMap.put(sourcePropertyId, listener); }); return () -> listenerMap.forEach((sourcePropertyId, listener) -> { - @SuppressWarnings("rawtypes") Field sourceField = fieldGroup.getField(sourcePropertyId); if (sourceField != null) { sourceField.removeValueChangeListener(listener); @@ -367,30 +370,19 @@ public static void setVisibleWhen(final Field targetField, Map onValueChangedSetVisible(targetField, sourceFieldsAndValues, clearOnHidden))); } + @SuppressWarnings("rawtypes") private static void onValueChangedSetVisible( final FieldGroup fieldGroup, - List targetPropertyIds, + List targetFields, Map> sourcePropertyIdsAndValues, final boolean clearOnHidden) { - //a workaround variable to be modified in the forEach lambda - boolean[] visibleArray = { - true }; - - sourcePropertyIdsAndValues.forEach((sourcePropertyId, sourceValues) -> { - @SuppressWarnings("rawtypes") - Field sourceField = fieldGroup.getField(sourcePropertyId); - if (sourceField == null || !sourceValues.contains(sourceField.getValue())) - visibleArray[0] = false; + boolean visible = sourcePropertyIdsAndValues.entrySet().stream().allMatch(entry -> { + Field sourceField = fieldGroup.getField(entry.getKey()); + return sourceField != null && entry.getValue().contains(sourceField.getValue()); }); - boolean visible = visibleArray[0]; - - for (Object targetPropertyId : targetPropertyIds) { - @SuppressWarnings("rawtypes") - Field targetField = fieldGroup.getField(targetPropertyId); - if (targetField == null) - continue; // defensive: field may have been unbound by a caller that did not remove its Registration + for (Field targetField : targetFields) { targetField.setVisible(visible); if (!visible && clearOnHidden && targetField.getValue() != null) { targetField.clear(); From 3f728f2917526e021a22c9cca84d2f6112f547c5 Mon Sep 17 00:00:00 2001 From: Harold Date: Fri, 27 Mar 2026 04:32:39 +0300 Subject: [PATCH 13/30] Migrates PathogenTestForm to use components using Vaadin 8 --- .../de/symeda/sormas/api/utils/Diseases.java | 6 + .../sormas/ui/samples/PathogenTestForm.java | 747 +++++------------- .../components/CtCqValueComponent.java | 123 +++ .../samples/components/DeletionComponent.java | 56 ++ .../components/DiseaseSelectionComponent.java | 192 +++++ .../components/PrescriberComponent.java | 90 +++ .../TestIdentificationComponent.java | 94 +++ .../components/TestMethodComponent.java | 269 +++++++ .../components/TestResultComponent.java | 213 +++++ .../AbstractDiseaseSectionComponent.java | 129 +++ .../CoronavirusSectionComponent.java | 59 ++ .../CryptosporidiosisSectionComponent.java | 116 +++ .../diseasesection/CsmSectionComponent.java | 61 ++ .../DefaultSectionComponent.java | 56 ++ .../diseasesection/DiseaseSectionFactory.java | 38 + .../diseasesection/ImiSectionComponent.java | 139 ++++ .../diseasesection/IpiSectionComponent.java | 151 ++++ .../MeaslesSectionComponent.java | 99 +++ .../TuberculosisSectionComponent.java | 345 ++++++++ .../samples/events/DiseaseChangedEvent.java | 16 + .../ui/samples/events/SetTestResultEvent.java | 21 + .../events/TestResultChangedEvent.java | 16 + .../samples/events/TestTypeChangedEvent.java | 16 + .../samples/events/ViaLimsChangedEvent.java | 14 + .../ui/therapy/DrugSusceptibilityForm.java | 42 +- .../symeda/sormas/ui/utils/FormComponent.java | 381 +++++++++ .../symeda/sormas/ui/utils/FormEventBus.java | 36 + .../utils/StringToFloatNullableConverter.java | 62 ++ 28 files changed, 3022 insertions(+), 565 deletions(-) create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/CtCqValueComponent.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/DeletionComponent.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/DiseaseSelectionComponent.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/PrescriberComponent.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/TestIdentificationComponent.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/TestMethodComponent.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/TestResultComponent.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/AbstractDiseaseSectionComponent.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CoronavirusSectionComponent.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CryptosporidiosisSectionComponent.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CsmSectionComponent.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/DefaultSectionComponent.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/DiseaseSectionFactory.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/ImiSectionComponent.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/IpiSectionComponent.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/MeaslesSectionComponent.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/TuberculosisSectionComponent.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/events/DiseaseChangedEvent.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/events/SetTestResultEvent.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/events/TestResultChangedEvent.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/events/TestTypeChangedEvent.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/samples/events/ViaLimsChangedEvent.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/utils/FormComponent.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/utils/FormEventBus.java create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/utils/StringToFloatNullableConverter.java diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/utils/Diseases.java b/sormas-api/src/main/java/de/symeda/sormas/api/utils/Diseases.java index 637cbcf84e9..3a13cf58a03 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/utils/Diseases.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/utils/Diseases.java @@ -90,6 +90,12 @@ public static boolean isDefined(Class clazz, String propertyName, Disease dis return diseaseConfig.get(propertyName).contains(disease); } + public static > List getVisibleValues(Class enumClass, Disease disease) { + return Arrays.stream(enumClass.getEnumConstants()) + .filter(e -> isDefinedOrMissing(enumClass, e.name(), disease)) + .collect(Collectors.toList()); + } + private static synchronized void readDiseaseConfig(Class clazz) { HashMap> diseaseConfig = new HashMap<>(); diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java index 7cec6333d6b..b29a4fdebc3 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/PathogenTestForm.java @@ -18,180 +18,83 @@ package de.symeda.sormas.ui.samples; import static de.symeda.sormas.ui.utils.CssStyles.H3; -import static de.symeda.sormas.ui.utils.CssStyles.VSPACE_3; -import static de.symeda.sormas.ui.utils.CssStyles.VSPACE_TOP_4; -import static de.symeda.sormas.ui.utils.LayoutUtil.fluidRowLocs; import static de.symeda.sormas.ui.utils.LayoutUtil.loc; -import java.time.LocalTime; -import java.time.ZoneId; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; import java.util.Date; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.function.Consumer; -import org.apache.commons.collections4.CollectionUtils; - -import com.vaadin.ui.CustomLayout; +import com.vaadin.shared.Registration; +import com.vaadin.ui.Component; import com.vaadin.ui.Label; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.v7.data.fieldgroup.FieldGroup.CommitEvent; +import com.vaadin.v7.data.fieldgroup.FieldGroup.CommitException; import com.vaadin.v7.data.util.converter.Converter; -import com.vaadin.v7.ui.AbstractSelect.ItemCaptionMode; -import com.vaadin.v7.ui.CheckBox; -import com.vaadin.v7.ui.ComboBox; -import com.vaadin.v7.ui.DateField; -import com.vaadin.v7.ui.TextArea; -import com.vaadin.v7.ui.TextField; import de.symeda.sormas.api.Disease; -import de.symeda.sormas.api.DiseaseHelper; import de.symeda.sormas.api.FacadeProvider; -import de.symeda.sormas.api.customizableenum.CustomizableEnumType; import de.symeda.sormas.api.disease.DiseaseVariant; import de.symeda.sormas.api.environment.environmentsample.EnvironmentSampleDto; -import de.symeda.sormas.api.environment.environmentsample.Pathogen; -import de.symeda.sormas.api.i18n.Captions; -import de.symeda.sormas.api.i18n.I18nProperties; -import de.symeda.sormas.api.i18n.Validations; -import de.symeda.sormas.api.infrastructure.facility.FacilityDto; -import de.symeda.sormas.api.infrastructure.facility.FacilityReferenceDto; import de.symeda.sormas.api.sample.PathogenTestDto; import de.symeda.sormas.api.sample.PathogenTestResultType; -import de.symeda.sormas.api.sample.PathogenTestType; import de.symeda.sormas.api.sample.SampleDto; import de.symeda.sormas.api.sample.SamplePurpose; import de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers; -import de.symeda.sormas.ui.samples.diseasesection.DefaultDiseaseSectionLayout; -import de.symeda.sormas.ui.samples.diseasesection.DiseaseSectionLayout; +import de.symeda.sormas.ui.samples.components.DeletionComponent; +import de.symeda.sormas.ui.samples.components.DiseaseSelectionComponent; +import de.symeda.sormas.ui.samples.components.PrescriberComponent; +import de.symeda.sormas.ui.samples.components.TestIdentificationComponent; +import de.symeda.sormas.ui.samples.components.TestMethodComponent; +import de.symeda.sormas.ui.samples.components.TestResultComponent; +import de.symeda.sormas.ui.samples.diseasesection.AbstractDiseaseSectionComponent; +import de.symeda.sormas.ui.samples.diseasesection.DiseaseSectionFactory; import de.symeda.sormas.ui.samples.diseasesection.PathogenTestFormConfig; +import de.symeda.sormas.ui.samples.events.DiseaseChangedEvent; import de.symeda.sormas.ui.utils.AbstractEditForm; -import de.symeda.sormas.ui.utils.CssStyles; -import de.symeda.sormas.ui.utils.DateComparisonValidator; -import de.symeda.sormas.ui.utils.DateFormatHelper; -import de.symeda.sormas.ui.utils.DateTimeField; import de.symeda.sormas.ui.utils.FieldAccessHelper; -import de.symeda.sormas.ui.utils.FieldConfiguration; -import de.symeda.sormas.ui.utils.FieldHelper; -import de.symeda.sormas.ui.utils.NullableOptionGroup; -import de.symeda.sormas.ui.utils.PhoneNumberValidator; - +import de.symeda.sormas.ui.utils.FormComponent; +import de.symeda.sormas.ui.utils.FormEventBus; + +/** + * Component-based PathogenTestForm using Vaadin 8 components. + *

    + * Extends {@link AbstractEditForm} for compatibility with + * {@link de.symeda.sormas.ui.utils.CommitDiscardWrapperComponent} and + * {@link de.symeda.sormas.ui.externalmessage.CorrectionPanel}. + *

    + * Each component owns its own Vaadin 8 Binder and manages its own field + * visibility via value change listeners (no FieldHelper.setVisibleWhen). + * The FieldGroup from AbstractEditForm is kept hollow — only DrugSusceptibilityForm + * (in disease sections) binds to it for legacy compatibility. + */ public class PathogenTestForm extends AbstractEditForm { private static final long serialVersionUID = -1218707278398543154L; - private static final String PATHOGEN_TEST_HEADING_LOC = "pathogenTestHeadingLoc"; - - private static final String PRESCRIBER_HEADING_LOC = "prescriberHeading"; - - private static final String DISEASE_SECTION_LOC = "diseaseSectionLoc"; - - private static final String[] CT_CQ_FIELD_IDS = { - PathogenTestDto.CQ_VALUE, - PathogenTestDto.CT_VALUE_E, - PathogenTestDto.CT_VALUE_N, - PathogenTestDto.CT_VALUE_RDRP, - PathogenTestDto.CT_VALUE_S, - PathogenTestDto.CT_VALUE_ORF_1, - PathogenTestDto.CT_VALUE_RDRP_S - }; - - //@formatter:off - private static final String HTML_LAYOUT = - loc(PATHOGEN_TEST_HEADING_LOC) + - fluidRowLocs(PathogenTestDto.REPORT_DATE, PathogenTestDto.VIA_LIMS) + - fluidRowLocs(PathogenTestDto.EXTERNAL_ID, PathogenTestDto.EXTERNAL_ORDER_ID) + - fluidRowLocs(PathogenTestDto.TESTED_DISEASE, PathogenTestDto.TESTED_DISEASE_DETAILS) + - fluidRowLocs(PathogenTestDto.TEST_TYPE, PathogenTestDto.TEST_TYPE_TEXT) + - fluidRowLocs(PathogenTestDto.TESTED_PATHOGEN, PathogenTestDto.TESTED_PATHOGEN_DETAILS) + - fluidRowLocs(PathogenTestDto.TYPING_ID, "") + - fluidRowLocs(PathogenTestDto.TEST_DATE_TIME, PathogenTestDto.LAB) + - fluidRowLocs("", PathogenTestDto.LAB_DETAILS) + - fluidRowLocs(6,PathogenTestDto.TEST_RESULT, 4, PathogenTestDto.TEST_RESULT_VERIFIED, 2,PathogenTestDto.PRELIMINARY) + - fluidRowLocs(PathogenTestDto.TESTED_DISEASE_VARIANT, PathogenTestDto.TESTED_DISEASE_VARIANT_DETAILS) + - loc(DISEASE_SECTION_LOC) + - fluidRowLocs(PathogenTestDto.FOUR_FOLD_INCREASE_ANTIBODY_TITER, "") + - fluidRowLocs(PathogenTestDto.CQ_VALUE, "") + - fluidRowLocs(PathogenTestDto.CT_VALUE_E, PathogenTestDto.CT_VALUE_N) + - fluidRowLocs(PathogenTestDto.CT_VALUE_RDRP, PathogenTestDto.CT_VALUE_S) + - fluidRowLocs(PathogenTestDto.CT_VALUE_ORF_1, PathogenTestDto.CT_VALUE_RDRP_S) + - fluidRowLocs(PathogenTestDto.TEST_RESULT_TEXT) + - fluidRowLocs(PRESCRIBER_HEADING_LOC) + - fluidRowLocs(PathogenTestDto.PRESCRIBER_PHYSICIAN_CODE, "") + - fluidRowLocs(PathogenTestDto.PRESCRIBER_FIRST_NAME, PathogenTestDto.PRESCRIBER_LAST_NAME) + - fluidRowLocs(PathogenTestDto.PRESCRIBER_PHONE_NUMBER, "") + - fluidRowLocs(PathogenTestDto.PRESCRIBER_ADDRESS, PathogenTestDto.PRESCRIBER_POSTAL_CODE) + - fluidRowLocs(PathogenTestDto.PRESCRIBER_CITY, PathogenTestDto.PRESCRIBER_COUNTRY) + - fluidRowLocs(PathogenTestDto.DELETION_REASON) + - fluidRowLocs(PathogenTestDto.OTHER_DELETION_REASON); - //@formatter:on - - // map to decide the result type field value and enable/disable state - public static final Map> RESULT_FIELD_DECISION_MAP = Collections.unmodifiableMap(new HashMap<>() { - - { - put( - Disease.INVASIVE_MENINGOCOCCAL_INFECTION, - new ArrayList<>( - List.of( - PathogenTestType.SEROGROUPING, - PathogenTestType.MULTILOCUS_SEQUENCE_TYPING, - PathogenTestType.SLIDE_AGGLUTINATION, - PathogenTestType.WHOLE_GENOME_SEQUENCING, - PathogenTestType.SEQUENCING, - PathogenTestType.ANTIBIOTIC_SUSCEPTIBILITY))); - put( - Disease.INVASIVE_PNEUMOCOCCAL_INFECTION, - new ArrayList<>( - List.of( - PathogenTestType.SEROGROUPING, - PathogenTestType.MULTILOCUS_SEQUENCE_TYPING, - PathogenTestType.SLIDE_AGGLUTINATION, - PathogenTestType.WHOLE_GENOME_SEQUENCING, - PathogenTestType.SEQUENCING, - PathogenTestType.ANTIBIOTIC_SUSCEPTIBILITY))); - put(Disease.MEASLES, new ArrayList<>(List.of(PathogenTestType.GENOTYPING))); - put(Disease.RESPIRATORY_SYNCYTIAL_VIRUS, new ArrayList<>(List.of(PathogenTestType.SEQUENCING, PathogenTestType.WHOLE_GENOME_SEQUENCING))); - put(Disease.INFLUENZA, new ArrayList<>(List.of(PathogenTestType.ISOLATION))); - put(Disease.CRYPTOSPORIDIOSIS, new ArrayList<>(List.of(PathogenTestType.GENOTYPING))); - } - }); + private static final String COMPONENT_CONTAINER_LOC = "componentContainerLoc"; + private static final String HTML_LAYOUT = loc(COMPONENT_CONTAINER_LOC); private SampleDto sample; private EnvironmentSampleDto environmentSample; private AbstractSampleForm sampleForm; private final int caseSampleCount; private final boolean create; - - private Label pathogenTestHeadingLabel; - - private ComboBox testTypeField; - private ComboBox diseaseField; - private ComboBox testResultField; - private TextField testTypeTextField; private Disease disease; - private TextField typingIdField; - - // New instance fields promoted from addFields() locals - private CheckBox viaLimsField; - private ComboBox lab; - private TextField labDetails; - private TextField cqValueField; - private NullableOptionGroup testResultVerifiedField; - private CheckBox fourFoldIncrease; - private Label prescriberHeadingLabel; - private ComboBox diseaseVariantField; - private TextField diseaseVariantDetailsField; - private Consumer updateDiseaseVariantField; - - // Disease section swap support - private DiseaseSectionLayout activeSection = new DefaultDiseaseSectionLayout(); - private CustomLayout diseaseSectionPanel; private PathogenTestFormConfig formConfig; + private final FormEventBus eventBus = new FormEventBus(); + private final List> formComponents = new ArrayList<>(); + private final List eventRegistrations = new ArrayList<>(); + + private Label headingLabel; + private DiseaseSelectionComponent diseaseSelectionComponent; + private TestMethodComponent testMethodComponent; + private TestResultComponent testResultComponent; + + private AbstractDiseaseSectionComponent activeSection; + private VerticalLayout diseaseSectionSlot; + public PathogenTestForm( AbstractSampleForm sampleForm, boolean create, @@ -199,6 +102,7 @@ public PathogenTestForm( boolean isPseudonymized, boolean inJurisdiction, Disease disease) { + this(create, caseSampleCount, isPseudonymized, inJurisdiction, disease); this.sampleForm = sampleForm; this.disease = disease; @@ -209,7 +113,6 @@ public PathogenTestForm( } public PathogenTestForm(SampleDto sample, boolean create, int caseSampleCount, boolean isPseudonymized, boolean inJurisdiction, Disease disease) { - this(create, caseSampleCount, isPseudonymized, inJurisdiction, disease); this.sample = sample; this.disease = disease; @@ -220,7 +123,6 @@ public PathogenTestForm(SampleDto sample, boolean create, int caseSampleCount, b } public PathogenTestForm(EnvironmentSampleDto sample, boolean create, boolean isPseudonymized, boolean inJurisdiction, Disease disease) { - this(create, 0, isPseudonymized, inJurisdiction, disease); this.environmentSample = sample; addFields(); @@ -229,477 +131,228 @@ public PathogenTestForm(EnvironmentSampleDto sample, boolean create, boolean isP } } - public PathogenTestForm(boolean create, int caseSampleCount, boolean isPseudonymized, boolean inJurisdiction, Disease disease) { + private PathogenTestForm(boolean create, int caseSampleCount, boolean isPseudonymized, boolean inJurisdiction, Disease disease) { super( PathogenTestDto.class, PathogenTestDto.I18N_PREFIX, false, FieldVisibilityCheckers.withDisease(disease).andWithCountry(FacadeProvider.getConfigFacade().getCountryLocale()), - FieldAccessHelper.getFieldAccessCheckers(create || inJurisdiction, !create && isPseudonymized));// Jurisdiction doesn't matter for creation forms // Pseudonymization doesn't matter for creation forms + FieldAccessHelper.getFieldAccessCheckers(create || inJurisdiction, !create && isPseudonymized)); this.caseSampleCount = caseSampleCount; this.create = create; + this.disease = disease; setWidth(900, Unit.PIXELS); } - private static void setCqValueVisibility( - ComboBox diseaseField, - TextField cqValueField, - PathogenTestType testType, - PathogenTestResultType testResultType) { - - if (diseaseField.getValue() == null || !List.of(Disease.TUBERCULOSIS).contains((Disease) diseaseField.getValue())) { - if (((testType == PathogenTestType.PCR_RT_PCR && testResultType == PathogenTestResultType.POSITIVE)) - || testType == PathogenTestType.CQ_VALUE_DETECTION) { - cqValueField.setVisible(true); - } else { - cqValueField.setVisible(false); - cqValueField.clear(); - } - } - } - - private Date getSampleDate() { - if (sample != null) { - return sample.getSampleDateTime(); - } - if (sampleForm != null) { - return (Date) sampleForm.getField(SampleDto.SAMPLE_DATE_TIME).getValue(); - } - if (environmentSample != null) { - return environmentSample.getSampleDateTime(); - } - return null; - } - - private SamplePurpose getSamplePurpose() { - if (sample != null) { - return sample.getSamplePurpose(); - } - if (sampleForm != null) { - return (SamplePurpose) sampleForm.getField(SampleDto.SAMPLE_PURPOSE).getValue(); - } - return null; - } - @Override protected String createHtmlLayout() { return HTML_LAYOUT; } @Override - public void setHeading(String heading) { - pathogenTestHeadingLabel.setValue(heading); - } + protected void addFields() { + formConfig = PathogenTestFormConfig.fromCurrentConfig(); - @Override - public void setValue(PathogenTestDto newFieldValue) throws ReadOnlyException, Converter.ConversionException { - super.setValue(newFieldValue); - testTypeField.setValue(newFieldValue.getTestType()); - testTypeTextField.setValue(newFieldValue.getTestTypeText()); - if (!testResultField.isReadOnly()) { - testResultField.setValue(newFieldValue.getTestResult()); - } - typingIdField.setValue(newFieldValue.getTypingId()); - ComboBox specieFieldDynamic = getField(PathogenTestDto.SPECIE); - if (specieFieldDynamic != null) { - specieFieldDynamic.setValue(newFieldValue.getSpecie()); - } - markAsDirty(); - } + VerticalLayout container = new VerticalLayout(); + container.setWidth(100, Unit.PERCENTAGE); + container.setMargin(false); + container.setSpacing(true); + getContent().addComponent(container, COMPONENT_CONTAINER_LOC); - @Override - protected void addFields() { - addHeaderAndLayoutFields(); - addTestDateField(); - addLabFields(); - addDiseaseAndResultFields(); - addCtValueFields(); - addPrescriberFields(); - bindVisibilityRules(); - bindValueChangeListeners(); - finalizeForm(); - } + headingLabel = new Label(); + headingLabel.addStyleName(H3); + container.addComponent(headingLabel); - private void addHeaderAndLayoutFields() { - formConfig = PathogenTestFormConfig.fromCurrentConfig(); + TestIdentificationComponent identificationComponent = new TestIdentificationComponent(eventBus); + formComponents.add(identificationComponent); + container.addComponent(identificationComponent); - pathogenTestHeadingLabel = new Label(); - pathogenTestHeadingLabel.addStyleName(H3); - getContent().addComponent(pathogenTestHeadingLabel, PATHOGEN_TEST_HEADING_LOC); - - // Install the disease section panel — a nested CustomLayout whose template is swapped on disease change - diseaseSectionPanel = new CustomLayout(); - diseaseSectionPanel.setTemplateContents(activeSection.getHtmlLayout()); - diseaseSectionPanel.setWidth(100, Unit.PERCENTAGE); - getContent().addComponent(diseaseSectionPanel, DISEASE_SECTION_LOC); - - addDateField(PathogenTestDto.REPORT_DATE, DateField.class, 0); - viaLimsField = addField(PathogenTestDto.VIA_LIMS); - addField(PathogenTestDto.EXTERNAL_ID); - addField(PathogenTestDto.EXTERNAL_ORDER_ID); - testTypeField = addField(PathogenTestDto.TEST_TYPE, ComboBox.class); - testTypeField.setItemCaptionMode(ItemCaptionMode.ID_TOSTRING); - testTypeField.setImmediate(true); - testTypeTextField = addField(PathogenTestDto.TEST_TYPE_TEXT, TextField.class); - } + diseaseSelectionComponent = new DiseaseSelectionComponent(eventBus, disease, create, environmentSample != null); + formComponents.add(diseaseSelectionComponent); + container.addComponent(diseaseSelectionComponent); - private DateTimeField addTestDateField() { - DateTimeField testDateField = addField(PathogenTestDto.TEST_DATE_TIME, DateTimeField.class); - testDateField.removeAllValidators(); - testDateField.addValidator( - new DateComparisonValidator( - testDateField, - this::getSampleDate, - false, - false, - true, - I18nProperties.getValidationError( - Validations.afterDateWithDate, - testDateField.getCaption(), - I18nProperties.getPrefixCaption(SampleDto.I18N_PREFIX, SampleDto.SAMPLE_DATE_TIME), - DateFormatHelper.formatDate(getSampleDate())))); - testDateField.addValueChangeListener(e -> { - boolean hasTime = - getSampleDate() != null && !getSampleDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate().equals(LocalTime.MIDNIGHT); - - if (!hasTime) { - return; - } + testMethodComponent = new TestMethodComponent(eventBus, this::getSampleDate); + formComponents.add(testMethodComponent); + container.addComponent(testMethodComponent); - testDateField.removeAllValidators(); - testDateField.addValidator( - new DateComparisonValidator( - testDateField, - this::getSampleDate, - false, - false, - false, - I18nProperties.getValidationError( - Validations.afterDateWithDate, - testDateField.getCaption(), - I18nProperties.getPrefixCaption(SampleDto.I18N_PREFIX, SampleDto.SAMPLE_DATE_TIME), - DateFormatHelper.formatLocalDateTime(getSampleDate())))); + diseaseSectionSlot = new VerticalLayout(); + diseaseSectionSlot.setWidth(100, Unit.PERCENTAGE); + diseaseSectionSlot.setMargin(false); + diseaseSectionSlot.setSpacing(false); + container.addComponent(diseaseSectionSlot); - }); - return testDateField; - } + activeSection = DiseaseSectionFactory.forDisease(disease); + activeSection.initialize(getFieldGroup(), eventBus, formConfig, disease); + activeSection.setVisibilityCallback(visible -> diseaseSectionSlot.setVisible(visible)); + diseaseSectionSlot.addComponent(activeSection); + + testResultComponent = new TestResultComponent(eventBus, caseSampleCount, formConfig.isLuxembourg, disease); + formComponents.add(testResultComponent); + container.addComponent(testResultComponent); + + PrescriberComponent prescriberComponent = new PrescriberComponent(); + formComponents.add(prescriberComponent); + container.addComponent(prescriberComponent); + + DeletionComponent deletionComponent = new DeletionComponent(); + formComponents.add(deletionComponent); + container.addComponent(deletionComponent); - private void addLabFields() { - lab = addInfrastructureField(PathogenTestDto.LAB); - lab.addItems(FacadeProvider.getFacilityFacade().getAllActiveLaboratories(true)); - labDetails = addField(PathogenTestDto.LAB_DETAILS, TextField.class); - labDetails.setVisible(false); - typingIdField = addField(PathogenTestDto.TYPING_ID, TextField.class); - typingIdField.setVisible(false); + wireEvents(); + + finalizeForm(); } - private void addDiseaseAndResultFields() { - // Tested Disease or Tested Pathogen, depending on sample type - diseaseField = addDiseaseField(PathogenTestDto.TESTED_DISEASE, true, create, false); - addField(PathogenTestDto.TESTED_DISEASE_DETAILS, TextField.class); - diseaseVariantField = addCustomizableEnumField(PathogenTestDto.TESTED_DISEASE_VARIANT); - diseaseVariantField.setNullSelectionAllowed(true); - diseaseVariantField.setVisible(false); - diseaseVariantDetailsField = addField(PathogenTestDto.TESTED_DISEASE_VARIANT_DETAILS, TextField.class); - diseaseVariantDetailsField.setVisible(false); - if (DiseaseHelper.SUBTYPE_ALLOWED_DISEASES.contains(disease)) { - diseaseVariantField.setCaption(I18nProperties.getCaption(Captions.PathogenTest_rsv_testedDiseaseVariant)); - diseaseVariantDetailsField.setCaption(I18nProperties.getCaption(Captions.PathogenTest_rsv_testedDiseaseVariantDetails)); - } - ComboBox testedPathogenField = addCustomizableEnumField(PathogenTestDto.TESTED_PATHOGEN); - TextField testedPathogenDetailsField = addField(PathogenTestDto.TESTED_PATHOGEN_DETAILS, TextField.class); - testedPathogenDetailsField.setVisible(false); - FieldHelper - .updateItems(testedPathogenField, FacadeProvider.getCustomizableEnumFacade().getEnumValues(CustomizableEnumType.PATHOGEN, disease)); - testedPathogenField.addValueChangeListener(e -> { - Pathogen pathogen = (Pathogen) e.getProperty().getValue(); - if (pathogen != null && pathogen.isHasDetails()) { - testedPathogenDetailsField.setVisible(true); + private void wireEvents() { + // Disease change → swap section + clear test type + eventRegistrations.add(eventBus.on(DiseaseChangedEvent.class, event -> { + Disease newDisease = event.getDisease(); + if (newDisease != disease) { + testMethodComponent.getTestTypeField().clear(); + } + disease = newDisease; + swapDiseaseSection(newDisease); + })); + + // Disease variant → auto-set test result + diseaseSelectionComponent.getDiseaseVariantField().addValueChangeListener(e -> { + DiseaseVariant variant = e.getValue(); + if (variant != null) { + testResultComponent.getTestResultField().setValue(PathogenTestResultType.POSITIVE); } else { - testedPathogenDetailsField.clear(); - testedPathogenDetailsField.setVisible(false); + testResultComponent.getTestResultField().clear(); } }); + } - if (environmentSample == null) { - diseaseField.setVisible(true); - diseaseField.setRequired(true); + private void finalizeForm() { + testMethodComponent.setLabRequired(SamplePurpose.INTERNAL.equals(getSamplePurpose())); - testedPathogenField.setVisible(false); - testedPathogenField.setRequired(false); - } else { - diseaseField.setVisible(false); - diseaseField.setRequired(false); + initializeAccessAndAllowedAccesses(); + initializeVisibilitiesAndAllowedVisibilities(); - testedPathogenField.setVisible(true); - testedPathogenField.setRequired(true); + for (FormComponent comp : formComponents) { + if (fieldVisibilityCheckers != null) { + comp.applyVisibility(fieldVisibilityCheckers, PathogenTestDto.class); + } + if (fieldAccessCheckers != null) { + comp.applyAccess(fieldAccessCheckers, PathogenTestDto.class); + } } - testResultField = addField(PathogenTestDto.TEST_RESULT, ComboBox.class); - testResultField.removeItem(PathogenTestResultType.NOT_DONE); - - if (!formConfig.isLuxembourg) { - testResultField.removeItem(PathogenTestResultType.NOT_APPLICABLE); + if (fieldVisibilityCheckers != null) { + activeSection.applyVisibility(fieldVisibilityCheckers, PathogenTestDto.class); } - // Bind the initial disease section (default = no-op; swapped via swapDiseaseSection() on disease change) - activeSection = DiseaseSectionLayout.forDisease(disease); - diseaseSectionPanel.setTemplateContents(activeSection.getHtmlLayout()); - activeSection.bindFields(getFieldGroup(), diseaseSectionPanel, disease, formConfig); - } - private void addCtValueFields() { - cqValueField = addField(FieldConfiguration.withConversionError(PathogenTestDto.CQ_VALUE, Validations.onlyNumbersAllowed)); - if (!formConfig.isLuxembourg) { - cqValueField.setVisible(false); + if (fieldAccessCheckers != null) { + activeSection.applyAccess(fieldAccessCheckers, PathogenTestDto.class); } - addFields( - FieldConfiguration.withConversionError(PathogenTestDto.CT_VALUE_E, Validations.onlyNumbersAllowed), - FieldConfiguration.withConversionError(PathogenTestDto.CT_VALUE_N, Validations.onlyNumbersAllowed), - FieldConfiguration.withConversionError(PathogenTestDto.CT_VALUE_RDRP, Validations.onlyNumbersAllowed), - FieldConfiguration.withConversionError(PathogenTestDto.CT_VALUE_S, Validations.onlyNumbersAllowed), - FieldConfiguration.withConversionError(PathogenTestDto.CT_VALUE_ORF_1, Validations.onlyNumbersAllowed), - FieldConfiguration.withConversionError(PathogenTestDto.CT_VALUE_RDRP_S, Validations.onlyNumbersAllowed)); - - setVisibleClear(false, CT_CQ_FIELD_IDS); } - private void addPrescriberFields() { - testResultVerifiedField = addField(PathogenTestDto.TEST_RESULT_VERIFIED, NullableOptionGroup.class); - addField(PathogenTestDto.PRELIMINARY).addStyleName(CssStyles.VSPACE_4); - - // Make TEST_RESULT_VERIFIED required only when the test comes via LIMS (laboratory is directly connected) - viaLimsField.addValueChangeListener(e -> { - boolean isViaLims = Boolean.TRUE.equals(e.getProperty().getValue()); - testResultVerifiedField.setRequired(isViaLims); - }); - - // Set initial required state based on current viaLims value - testResultVerifiedField.setRequired(Boolean.TRUE.equals(viaLimsField.getValue())); - - fourFoldIncrease = addField(PathogenTestDto.FOUR_FOLD_INCREASE_ANTIBODY_TITER, CheckBox.class); - CssStyles.style(fourFoldIncrease, VSPACE_3, VSPACE_TOP_4); - fourFoldIncrease.setVisible(false); - fourFoldIncrease.setEnabled(false); + private void swapDiseaseSection(Disease newDisease) { + AbstractDiseaseSectionComponent newSection = DiseaseSectionFactory.forDisease(newDisease); + if (newSection.getClass() == activeSection.getClass()) { + return; + } - addField(PathogenTestDto.TEST_RESULT_TEXT, TextArea.class).setRows(6); + activeSection.cleanup(); + diseaseSectionSlot.removeAllComponents(); - addFields(PathogenTestDto.PRESCRIBER_PHYSICIAN_CODE, PathogenTestDto.PRESCRIBER_FIRST_NAME, PathogenTestDto.PRESCRIBER_LAST_NAME); - TextField proscriberPhoneField = addField(PathogenTestDto.PRESCRIBER_PHONE_NUMBER, TextField.class); - proscriberPhoneField.addValidator( - new PhoneNumberValidator(I18nProperties.getValidationError(Validations.validPhoneNumber, proscriberPhoneField.getCaption()))); + activeSection = newSection; + activeSection.initialize(getFieldGroup(), eventBus, formConfig, newDisease); + activeSection.setVisibilityCallback(visible -> diseaseSectionSlot.setVisible(visible)); + diseaseSectionSlot.addComponent(activeSection); - addFields(PathogenTestDto.PRESCRIBER_ADDRESS, PathogenTestDto.PRESCRIBER_POSTAL_CODE, PathogenTestDto.PRESCRIBER_CITY); - ComboBox prescriberCountrField = addInfrastructureField(PathogenTestDto.PRESCRIBER_COUNTRY); - FieldHelper.updateItems(prescriberCountrField, FacadeProvider.getCountryFacade().getAllActiveAsReference()); + PathogenTestDto dto = getValue(); + if (dto != null) { + activeSection.setDto(dto); + } - addField(PathogenTestDto.DELETION_REASON); - addField(PathogenTestDto.OTHER_DELETION_REASON, TextArea.class).setRows(3); - setVisible(false, PathogenTestDto.DELETION_REASON, PathogenTestDto.OTHER_DELETION_REASON); + if (fieldVisibilityCheckers != null) { + activeSection.applyVisibility(fieldVisibilityCheckers, PathogenTestDto.class); + } - prescriberHeadingLabel = new Label(I18nProperties.getCaption(Captions.PathogenTest_prescriber)); - prescriberHeadingLabel.addStyleName(H3); - getContent().addComponent(prescriberHeadingLabel, PRESCRIBER_HEADING_LOC); + if (fieldAccessCheckers != null) { + activeSection.applyAccess(fieldAccessCheckers, PathogenTestDto.class); + } } - private void bindVisibilityRules() { - FieldHelper.setVisibleWhen( - getFieldGroup(), - PathogenTestDto.TEST_TYPE_TEXT, - PathogenTestDto.TEST_TYPE, - Arrays.asList(PathogenTestType.PCR_RT_PCR, PathogenTestType.OTHER), - true); - FieldHelper.setVisibleWhen( - getFieldGroup(), - PathogenTestDto.TESTED_DISEASE_DETAILS, - PathogenTestDto.TESTED_DISEASE, - Arrays.asList(Disease.OTHER), - true); - FieldHelper.setVisibleWhen( - getFieldGroup(), - PathogenTestDto.TYPING_ID, - PathogenTestDto.TEST_TYPE, - Arrays.asList(PathogenTestType.PCR_RT_PCR, PathogenTestType.DNA_MICROARRAY, PathogenTestType.SEQUENCING), - true); - - //disease variant specifications for RSV and Influenza - Map> diseaseVariantDependencies = new HashMap<>() { - - { - put(PathogenTestDto.TESTED_DISEASE, Arrays.asList(Disease.RESPIRATORY_SYNCYTIAL_VIRUS, Disease.INFLUENZA)); - put( - PathogenTestDto.TEST_TYPE, - Arrays.asList( - PathogenTestType.SEQUENCING, - PathogenTestType.WHOLE_GENOME_SEQUENCING, - PathogenTestType.PCR_RT_PCR, - PathogenTestType.ISOLATION, - PathogenTestType.OTHER)); - } - }; - FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.TESTED_DISEASE_VARIANT, diseaseVariantDependencies, true); - - updateDiseaseVariantField = d -> { - List diseaseVariants = FacadeProvider.getCustomizableEnumFacade().getEnumValues(CustomizableEnumType.DISEASE_VARIANT, d); - FieldHelper.updateItems(diseaseVariantField, diseaseVariants); - diseaseVariantField - .setVisible(d != null && isVisibleAllowed(PathogenTestDto.TESTED_DISEASE_VARIANT) && CollectionUtils.isNotEmpty(diseaseVariants)); - }; - - updateDiseaseVariantField.accept((Disease) diseaseField.getValue()); + private Date getSampleDate() { + if (sample != null) { + return sample.getSampleDateTime(); + } + if (sampleForm != null) { + return (Date) sampleForm.getField(SampleDto.SAMPLE_DATE_TIME).getValue(); + } + if (environmentSample != null) { + return environmentSample.getSampleDateTime(); + } + return null; } - private void bindValueChangeListeners() { - diseaseField.addValueChangeListener(valueChangeEvent -> { - Disease latestDisease = (Disease) valueChangeEvent.getProperty().getValue(); - // If the disease changed, test type field should be updated with its respective test types - if (latestDisease != disease) { - testTypeField.clear(); - } - disease = latestDisease; - updateDiseaseVariantField.accept(disease); - swapDiseaseSection(latestDisease); - - FieldHelper.updateItems( - testTypeField, - Arrays.asList(PathogenTestType.values()), - FieldVisibilityCheckers.withDisease(disease), - PathogenTestType.class); - }); - diseaseVariantField.addValueChangeListener(e -> { - DiseaseVariant diseaseVariant = (DiseaseVariant) e.getProperty().getValue(); - if (diseaseVariant != null) { - testResultField.setValue(PathogenTestResultType.POSITIVE); - } else { - testResultField.clear(); - } - diseaseVariantDetailsField.setVisible(diseaseVariant != null && diseaseVariant.matchPropertyValue(DiseaseVariant.HAS_DETAILS, true)); - }); - - testTypeField.addValueChangeListener(e -> { - PathogenTestType testType = (PathogenTestType) e.getProperty().getValue(); - if (testType != null) { - if (testType == PathogenTestType.IGM_SERUM_ANTIBODY || testType == PathogenTestType.IGG_SERUM_ANTIBODY) { - fourFoldIncrease.setVisible(true); - fourFoldIncrease.setEnabled(caseSampleCount >= 2); - } else { - fourFoldIncrease.setVisible(false); - fourFoldIncrease.setEnabled(false); - } - - if (diseaseField.getValue() == null || !List.of(Disease.TUBERCULOSIS).contains((Disease) diseaseField.getValue())) { - setVisibleClear(PathogenTestType.PCR_RT_PCR == testType, CT_CQ_FIELD_IDS); - } else { - setVisibleClear(false, CT_CQ_FIELD_IDS); - } - } else { - testResultField.clear(); - testResultField.setEnabled(true); - setVisibleClear(false, CT_CQ_FIELD_IDS); - } - - if (RESULT_FIELD_DECISION_MAP.containsKey(disease) && RESULT_FIELD_DECISION_MAP.get(disease).contains(testType)) { - testResultField.setValue(PathogenTestResultType.POSITIVE); - } else { - testResultField.clear(); - } - - activeSection.onTestTypeChanged( - testType, - (Disease) diseaseField.getValue(), - (com.vaadin.v7.ui.AbstractField) (Object) testResultField, - formConfig); - }); - - lab.addValueChangeListener(event -> { - if (event.getProperty().getValue() != null - && ((FacilityReferenceDto) event.getProperty().getValue()).getUuid().equals(FacilityDto.OTHER_FACILITY_UUID)) { - labDetails.setVisible(true); - labDetails.setRequired(isEditableAllowed(labDetails)); - } else { - labDetails.setVisible(false); - labDetails.setRequired(false); - labDetails.clear(); - } - }); - - testTypeField.addValueChangeListener(e -> { - PathogenTestType testType = (PathogenTestType) e.getProperty().getValue(); - setCqValueVisibility(diseaseField, cqValueField, testType, (PathogenTestResultType) testResultField.getValue()); - }); - - testResultField.addValueChangeListener(e -> { - PathogenTestResultType testResult = (PathogenTestResultType) e.getProperty().getValue(); - setCqValueVisibility(diseaseField, cqValueField, (PathogenTestType) testTypeField.getValue(), testResult); - }); + private SamplePurpose getSamplePurpose() { + if (sample != null) { + return sample.getSamplePurpose(); + } + if (sampleForm != null) { + return (SamplePurpose) sampleForm.getField(SampleDto.SAMPLE_PURPOSE).getValue(); + } + return null; } - private void finalizeForm() { - if (SamplePurpose.INTERNAL.equals(getSamplePurpose())) { // this only works for already saved samples - setRequired(true, PathogenTestDto.LAB); + @Override + public void preCommit(CommitEvent commitEvent) throws CommitException { + super.preCommit(commitEvent); + + for (FormComponent comp : formComponents) { + comp.validate(); } - setRequired(true, PathogenTestDto.TEST_TYPE, PathogenTestDto.TEST_RESULT); - initializeAccessAndAllowedAccesses(); - initializeVisibilitiesAndAllowedVisibilities(); + if (activeSection != null) { + activeSection.validate(); + } + } - // Hide/show prescriber heading after the visibilities have been initialized - prescriberHeadingLabel.setVisible( - isVisibleAllowed(PathogenTestDto.PRESCRIBER_PHYSICIAN_CODE) - || isVisibleAllowed(PathogenTestDto.PRESCRIBER_FIRST_NAME) - || isVisibleAllowed(PathogenTestDto.PRESCRIBER_LAST_NAME) - || isVisibleAllowed(PathogenTestDto.PRESCRIBER_PHONE_NUMBER) - || isVisibleAllowed(PathogenTestDto.PRESCRIBER_ADDRESS) - || isVisibleAllowed(PathogenTestDto.PRESCRIBER_POSTAL_CODE) - || isVisibleAllowed(PathogenTestDto.PRESCRIBER_CITY) - || isVisibleAllowed(PathogenTestDto.PRESCRIBER_COUNTRY)); + @Override + public void setHeading(String heading) { + headingLabel.setValue(heading); } - /** Replaces the active disease section with the one appropriate for the given disease. */ - private void swapDiseaseSection(Disease newDisease) { - DiseaseSectionLayout newSection = DiseaseSectionLayout.forDisease(newDisease); - if (newSection.getClass() == activeSection.getClass()) { - return; // same section type, nothing to swap - } + @Override + public void setValue(PathogenTestDto newFieldValue) throws ReadOnlyException, Converter.ConversionException { + super.setValue(newFieldValue); - removeFromAllowedLists(activeSection.getFieldIds()); - activeSection.unbindFields(getFieldGroup(), diseaseSectionPanel); - activeSection = newSection; + for (FormComponent comp : formComponents) { + comp.setDto(newFieldValue); + } - // Vaadin's CustomLayout.setTemplateContents() only works before the component - // is attached. Replace the entire panel so the new template renders correctly. - CustomLayout newPanel = new CustomLayout(); - newPanel.setTemplateContents(newSection.getHtmlLayout()); - newPanel.setWidth(100, Unit.PERCENTAGE); - getContent().addComponent(newPanel, DISEASE_SECTION_LOC); - diseaseSectionPanel = newPanel; + if (activeSection != null) { + activeSection.setDto(newFieldValue); + } - newSection.bindFields(getFieldGroup(), diseaseSectionPanel, newDisease, formConfig); - rebuildSectionFieldAllowances(newSection.getFieldIds()); + markAsDirty(); } - @SuppressWarnings("unchecked") - private void rebuildSectionFieldAllowances(Collection fieldIds) { - for (String id : fieldIds) { - com.vaadin.v7.ui.Field f = getField(id); - if (f != null) { - addToVisibleAllowedFields(f); - if (fieldAccessCheckers == null || fieldAccessCheckers.isAccessible(getType(), id)) { - addToEditableAllowedFields(f); - } - } + @Override + public void detach() { + for (Registration reg : eventRegistrations) { + reg.remove(); } + eventRegistrations.clear(); + + super.detach(); } - private void removeFromAllowedLists(Collection fieldIds) { - for (String id : fieldIds) { - com.vaadin.v7.ui.Field f = getField(id); - if (f != null) { - removeFromVisibleAllowedFields(f); - removeFromEditableAllowedFields(f); + @Override + public void forEachComponent(java.util.function.Consumer componentConsumer) { + Component c = getContent().getComponent(COMPONENT_CONTAINER_LOC); + if (c instanceof VerticalLayout) { + VerticalLayout vl = (VerticalLayout) c; + for (int i = 0; i < vl.getComponentCount(); i++) { + componentConsumer.accept(vl.getComponent(i)); } } } - } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/CtCqValueComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/CtCqValueComponent.java new file mode 100644 index 00000000000..6f7c045f455 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/CtCqValueComponent.java @@ -0,0 +1,123 @@ +package de.symeda.sormas.ui.samples.components; + +import com.vaadin.data.ValueProvider; +import com.vaadin.server.Setter; +import com.vaadin.shared.ui.ValueChangeMode; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.TextField; + +import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.api.sample.PathogenTestResultType; +import de.symeda.sormas.api.sample.PathogenTestType; +import de.symeda.sormas.ui.utils.FormComponent; +import de.symeda.sormas.ui.utils.StringToFloatNullableConverter; + +/** + * CQ value and CT values (E, N, RdRp, S, ORF1, RdRp/S) as a standalone composable component. + */ +public class CtCqValueComponent extends FormComponent { + + private static final long serialVersionUID = 1L; + + private final boolean isLuxembourg; + + private TextField cqValueField; + private TextField ctValueE; + private TextField ctValueN; + private TextField ctValueRdrp; + private TextField ctValueS; + private TextField ctValueOrf1; + private TextField ctValueRdrpS; + + private HorizontalLayout cqRow; + + public CtCqValueComponent(boolean isLuxembourg) { + super(PathogenTestDto.class); + this.isLuxembourg = isLuxembourg; + buildLayout(); + bindFields(); + } + + private void buildLayout() { + // CQ value + cqValueField = createTextField(PathogenTestDto.CQ_VALUE, PathogenTestDto.I18N_PREFIX, ValueChangeMode.BLUR); + if (!isLuxembourg) { + cqValueField.setVisible(false); + } + cqRow = addRow(cqValueField); + + // CT values + ctValueE = createTextField(PathogenTestDto.CT_VALUE_E, PathogenTestDto.I18N_PREFIX, ValueChangeMode.BLUR); + ctValueN = createTextField(PathogenTestDto.CT_VALUE_N, PathogenTestDto.I18N_PREFIX, ValueChangeMode.BLUR); + addRow(ctValueE, ctValueN); + + ctValueRdrp = createTextField(PathogenTestDto.CT_VALUE_RDRP, PathogenTestDto.I18N_PREFIX, ValueChangeMode.BLUR); + ctValueS = createTextField(PathogenTestDto.CT_VALUE_S, PathogenTestDto.I18N_PREFIX, ValueChangeMode.BLUR); + addRow(ctValueRdrp, ctValueS); + + ctValueOrf1 = createTextField(PathogenTestDto.CT_VALUE_ORF_1, PathogenTestDto.I18N_PREFIX, ValueChangeMode.BLUR); + ctValueRdrpS = createTextField(PathogenTestDto.CT_VALUE_RDRP_S, PathogenTestDto.I18N_PREFIX, ValueChangeMode.BLUR); + addRow(ctValueOrf1, ctValueRdrpS); + + // Initially hide CT fields + setCtFieldsVisible(false); + } + + private void bindFields() { + bindFloatField(cqValueField, PathogenTestDto::getCqValue, PathogenTestDto::setCqValue); + bindFloatField(ctValueE, PathogenTestDto::getCtValueE, PathogenTestDto::setCtValueE); + bindFloatField(ctValueN, PathogenTestDto::getCtValueN, PathogenTestDto::setCtValueN); + bindFloatField(ctValueRdrp, PathogenTestDto::getCtValueRdrp, PathogenTestDto::setCtValueRdrp); + bindFloatField(ctValueS, PathogenTestDto::getCtValueS, PathogenTestDto::setCtValueS); + bindFloatField(ctValueOrf1, PathogenTestDto::getCtValueOrf1, PathogenTestDto::setCtValueOrf1); + bindFloatField(ctValueRdrpS, PathogenTestDto::getCtValueRdrpS, PathogenTestDto::setCtValueRdrpS); + } + + private void bindFloatField(TextField field, ValueProvider getter, Setter setter) { + binder.forField(field).withConverter(new StringToFloatNullableConverter(field.getCaption())).bind(getter, setter); + } + + public void updateCtVisibility(Disease disease, PathogenTestType testType) { + if (disease == null || !java.util.Arrays.asList(Disease.TUBERCULOSIS).contains(disease)) { + setCtFieldsVisible(PathogenTestType.PCR_RT_PCR == testType); + } else { + setCtFieldsVisible(false); + } + } + + public void updateCqVisibility(Disease disease, PathogenTestType testType, PathogenTestResultType testResult) { + if (disease == null || !java.util.Arrays.asList(Disease.TUBERCULOSIS).contains(disease)) { + if ((testType == PathogenTestType.PCR_RT_PCR && testResult == PathogenTestResultType.POSITIVE) + || testType == PathogenTestType.CQ_VALUE_DETECTION) { + cqValueField.setVisible(true); + } else { + cqValueField.setVisible(false); + cqValueField.clear(); + } + } else { + cqValueField.setVisible(false); + cqValueField.clear(); + } + updateRowVisibility(cqRow); + updateRowAndSelfVisibility(); + } + + private void setCtFieldsVisible(boolean visible) { + TextField[] ctFields = { + ctValueE, + ctValueN, + ctValueRdrp, + ctValueS, + ctValueOrf1, + ctValueRdrpS }; + for (TextField f : ctFields) { + if (!visible) { + f.clear(); + } + f.setVisible(visible); + } + updateRowAndSelfVisibility(); + } + +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/DeletionComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/DeletionComponent.java new file mode 100644 index 00000000000..5e23598690a --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/DeletionComponent.java @@ -0,0 +1,56 @@ +package de.symeda.sormas.ui.samples.components; + +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.TextArea; + +import de.symeda.sormas.api.common.DeletionReason; +import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.ui.utils.FormComponent; + +/** + * Deletion reason fields using Vaadin 8 components with own Binder. + * Self-manages visibility of otherDeletionReason based on deletionReason value. + */ +public class DeletionComponent extends FormComponent { + + private static final long serialVersionUID = 1L; + + private ComboBox deletionReasonField; + private TextArea otherReasonField; + + public DeletionComponent() { + super(PathogenTestDto.class); + buildLayout(); + bindFields(); + } + + private void buildLayout() { + deletionReasonField = createComboBox(PathogenTestDto.DELETION_REASON, PathogenTestDto.I18N_PREFIX); + deletionReasonField.setItems(DeletionReason.values()); + deletionReasonField.setItemCaptionGenerator(DeletionReason::toString); + addFullWidthRow(deletionReasonField); + + otherReasonField = createTextArea(PathogenTestDto.OTHER_DELETION_REASON, PathogenTestDto.I18N_PREFIX); + otherReasonField.setRows(3); + addFullWidthRow(otherReasonField); + + // Hidden by default + deletionReasonField.setVisible(false); + otherReasonField.setVisible(false); + + // Self-managed visibility: show otherReason only when OTHER_REASON selected + track(deletionReasonField.addValueChangeListener(e -> { + boolean showOther = e.getValue() == DeletionReason.OTHER_REASON; + otherReasonField.setVisible(showOther); + if (!showOther) { + otherReasonField.clear(); + } + updateRowAndSelfVisibility(); + })); + } + + private void bindFields() { + binder.forField(deletionReasonField).bind(PathogenTestDto::getDeletionReason, PathogenTestDto::setDeletionReason); + binder.forField(otherReasonField).bind(PathogenTestDto::getOtherDeletionReason, PathogenTestDto::setOtherDeletionReason); + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/DiseaseSelectionComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/DiseaseSelectionComponent.java new file mode 100644 index 00000000000..a738d34a773 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/DiseaseSelectionComponent.java @@ -0,0 +1,192 @@ +package de.symeda.sormas.ui.samples.components; + +import java.util.List; + +import org.apache.commons.collections4.CollectionUtils; + +import com.vaadin.shared.ui.ValueChangeMode; +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.TextField; + +import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.DiseaseHelper; +import de.symeda.sormas.api.FacadeProvider; +import de.symeda.sormas.api.customizableenum.CustomizableEnumType; +import de.symeda.sormas.api.disease.DiseaseVariant; +import de.symeda.sormas.api.environment.environmentsample.Pathogen; +import de.symeda.sormas.api.i18n.Captions; +import de.symeda.sormas.api.i18n.I18nProperties; +import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.ui.samples.events.DiseaseChangedEvent; +import de.symeda.sormas.ui.utils.FormComponent; +import de.symeda.sormas.ui.utils.FormEventBus; + +/** + * Disease/pathogen selection with variant support. + * Vaadin 8 components with own Binder, self-managed visibility. + */ +public class DiseaseSelectionComponent extends FormComponent { + + private static final long serialVersionUID = 1L; + + private final FormEventBus eventBus; + private final Disease initialDisease; + private final boolean create; + private final boolean isEnvironmentSample; + + private ComboBox diseaseField; + private TextField diseaseDetailsField; + private Label diseaseDetailsSpacer; + private ComboBox testedPathogenField; + private TextField testedPathogenDetailsField; + private Label pathogenDetailsSpacer; + private ComboBox diseaseVariantField; + private TextField diseaseVariantDetailsField; + private Label variantDetailsSpacer; + + private HorizontalLayout variantRow; + + public DiseaseSelectionComponent(FormEventBus eventBus, Disease initialDisease, boolean create, boolean isEnvironmentSample) { + super(PathogenTestDto.class); + this.eventBus = eventBus; + this.initialDisease = initialDisease; + this.create = create; + this.isEnvironmentSample = isEnvironmentSample; + buildLayout(); + bindFields(); + wireEvents(); + } + + private void buildLayout() { + // Disease + diseaseField = createComboBox(PathogenTestDto.TESTED_DISEASE, PathogenTestDto.I18N_PREFIX); + diseaseField.setItemCaptionGenerator(Disease::toString); + + List activeDiseases = FacadeProvider.getDiseaseConfigurationFacade().getAllDiseases(true, true, true); + List nonPrimary = FacadeProvider.getDiseaseConfigurationFacade().getAllDiseases(true, false, true); + activeDiseases.addAll(nonPrimary); + diseaseField.setItems(activeDiseases); + + diseaseDetailsField = createTextField(PathogenTestDto.TESTED_DISEASE_DETAILS, PathogenTestDto.I18N_PREFIX, ValueChangeMode.BLUR); + diseaseDetailsField.setVisible(false); + + diseaseDetailsSpacer = createSpacer(); + addToggleRow(diseaseField, diseaseDetailsField, diseaseDetailsSpacer); + + // Pathogen + testedPathogenField = createComboBox(PathogenTestDto.TESTED_PATHOGEN, PathogenTestDto.I18N_PREFIX); + testedPathogenField.setItemCaptionGenerator(Pathogen::getCaption); + testedPathogenField.setItems(FacadeProvider.getCustomizableEnumFacade().getEnumValues(CustomizableEnumType.PATHOGEN, initialDisease)); + + testedPathogenDetailsField = createTextField(PathogenTestDto.TESTED_PATHOGEN_DETAILS, PathogenTestDto.I18N_PREFIX, ValueChangeMode.BLUR); + testedPathogenDetailsField.setVisible(false); + + pathogenDetailsSpacer = createSpacer(); + addToggleRow(testedPathogenField, testedPathogenDetailsField, pathogenDetailsSpacer); + + // Disease variant + diseaseVariantField = createComboBox(PathogenTestDto.TESTED_DISEASE_VARIANT, PathogenTestDto.I18N_PREFIX); + diseaseVariantField.setItemCaptionGenerator(DiseaseVariant::getCaption); + diseaseVariantField.setEmptySelectionAllowed(true); + diseaseVariantField.setVisible(false); + + diseaseVariantDetailsField = + createTextField(PathogenTestDto.TESTED_DISEASE_VARIANT_DETAILS, PathogenTestDto.I18N_PREFIX, ValueChangeMode.BLUR); + diseaseVariantDetailsField.setVisible(false); + + if (DiseaseHelper.SUBTYPE_ALLOWED_DISEASES.contains(initialDisease)) { + diseaseVariantField.setCaption(I18nProperties.getCaption(Captions.PathogenTest_rsv_testedDiseaseVariant)); + diseaseVariantDetailsField.setCaption(I18nProperties.getCaption(Captions.PathogenTest_rsv_testedDiseaseVariantDetails)); + } + + variantDetailsSpacer = createSpacer(); + variantRow = addToggleRow(diseaseVariantField, diseaseVariantDetailsField, variantDetailsSpacer); + + // Environment vs human sample visibility + if (isEnvironmentSample) { + diseaseField.setVisible(false); + diseaseDetailsField.setVisible(false); + testedPathogenField.setVisible(true); + } else { + diseaseField.setVisible(true); + testedPathogenField.setVisible(false); + } + + updateDiseaseVariants(initialDisease); + } + + private void bindFields() { + if (isEnvironmentSample) { + binder.forField(diseaseField).bind(PathogenTestDto::getTestedDisease, PathogenTestDto::setTestedDisease); + } else { + binder.forField(diseaseField).asRequired().bind(PathogenTestDto::getTestedDisease, PathogenTestDto::setTestedDisease); + } + binder.forField(diseaseDetailsField).bind(PathogenTestDto::getTestedDiseaseDetails, PathogenTestDto::setTestedDiseaseDetails); + if (isEnvironmentSample) { + binder.forField(testedPathogenField).asRequired().bind(PathogenTestDto::getTestedPathogen, PathogenTestDto::setTestedPathogen); + } else { + binder.forField(testedPathogenField).bind(PathogenTestDto::getTestedPathogen, PathogenTestDto::setTestedPathogen); + } + binder.forField(testedPathogenDetailsField).bind(PathogenTestDto::getTestedPathogenDetails, PathogenTestDto::setTestedPathogenDetails); + binder.forField(diseaseVariantField).bind(PathogenTestDto::getTestedDiseaseVariant, PathogenTestDto::setTestedDiseaseVariant); + binder.forField(diseaseVariantDetailsField) + .bind(PathogenTestDto::getTestedDiseaseVariantDetails, PathogenTestDto::setTestedDiseaseVariantDetails); + } + + private void wireEvents() { + // Disease change: update variants, show/hide details, fire event + track(diseaseField.addValueChangeListener(e -> { + Disease newDisease = e.getValue(); + + boolean showDiseaseDetails = newDisease == Disease.OTHER; + diseaseDetailsField.setVisible(showDiseaseDetails); + diseaseDetailsSpacer.setVisible(!showDiseaseDetails); + if (!showDiseaseDetails) { + diseaseDetailsField.clear(); + } + + updateDiseaseVariants(newDisease); + eventBus.fire(new DiseaseChangedEvent(newDisease)); + })); + + // Pathogen details visibility + track(testedPathogenField.addValueChangeListener(e -> { + Pathogen pathogen = e.getValue(); + boolean showPathogenDetails = pathogen != null && pathogen.isHasDetails(); + testedPathogenDetailsField.setVisible(showPathogenDetails); + pathogenDetailsSpacer.setVisible(!showPathogenDetails); + if (!showPathogenDetails) { + testedPathogenDetailsField.clear(); + } + })); + + // Disease variant details visibility + track(diseaseVariantField.addValueChangeListener(e -> { + DiseaseVariant variant = e.getValue(); + boolean showVariantDetails = variant != null && variant.matchPropertyValue(DiseaseVariant.HAS_DETAILS, true); + diseaseVariantDetailsField.setVisible(showVariantDetails); + variantDetailsSpacer.setVisible(!showVariantDetails); + })); + } + + private void updateDiseaseVariants(Disease disease) { + List variants = FacadeProvider.getCustomizableEnumFacade().getEnumValues(CustomizableEnumType.DISEASE_VARIANT, disease); + diseaseVariantField.setItems(variants); + diseaseVariantField.setVisible(disease != null && CollectionUtils.isNotEmpty(variants)); + updateRowVisibility(variantRow); + } + + public ComboBox getDiseaseField() { + return diseaseField; + } + + public ComboBox getDiseaseVariantField() { + return diseaseVariantField; + } + + public TextField getDiseaseVariantDetailsField() { + return diseaseVariantDetailsField; + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/PrescriberComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/PrescriberComponent.java new file mode 100644 index 00000000000..8d046e7e63d --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/PrescriberComponent.java @@ -0,0 +1,90 @@ +package de.symeda.sormas.ui.samples.components; + +import static de.symeda.sormas.ui.utils.CssStyles.H3; + +import com.vaadin.shared.ui.ValueChangeMode; +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.Label; +import com.vaadin.ui.TextField; + +import de.symeda.sormas.api.FacadeProvider; +import de.symeda.sormas.api.i18n.Captions; +import de.symeda.sormas.api.i18n.I18nProperties; +import de.symeda.sormas.api.i18n.Validations; +import de.symeda.sormas.api.infrastructure.country.CountryReferenceDto; +import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.api.utils.DataHelper; +import de.symeda.sormas.ui.utils.FormComponent; + +/** + * Prescriber fields section using Vaadin 8 components with own Binder. + */ +public class PrescriberComponent extends FormComponent { + + private static final long serialVersionUID = 1L; + + private Label heading; + private TextField physicianCode; + private TextField firstName; + private TextField lastName; + private TextField phone; + private TextField address; + private TextField postalCode; + private TextField city; + private ComboBox country; + + public PrescriberComponent() { + super(PathogenTestDto.class); + buildLayout(); + bindFields(); + } + + private void buildLayout() { + heading = new Label(I18nProperties.getCaption(Captions.PathogenTest_prescriber)); + heading.addStyleName(H3); + addComponent(heading); + + physicianCode = createTextField(PathogenTestDto.PRESCRIBER_PHYSICIAN_CODE, PathogenTestDto.I18N_PREFIX, ValueChangeMode.BLUR); + addRow(physicianCode); + + firstName = createTextField(PathogenTestDto.PRESCRIBER_FIRST_NAME, PathogenTestDto.I18N_PREFIX, ValueChangeMode.BLUR); + lastName = createTextField(PathogenTestDto.PRESCRIBER_LAST_NAME, PathogenTestDto.I18N_PREFIX, ValueChangeMode.BLUR); + addRow(firstName, lastName); + + phone = createTextField(PathogenTestDto.PRESCRIBER_PHONE_NUMBER, PathogenTestDto.I18N_PREFIX, ValueChangeMode.BLUR); + addRow(phone); + + address = createTextField(PathogenTestDto.PRESCRIBER_ADDRESS, PathogenTestDto.I18N_PREFIX, ValueChangeMode.BLUR); + postalCode = createTextField(PathogenTestDto.PRESCRIBER_POSTAL_CODE, PathogenTestDto.I18N_PREFIX, ValueChangeMode.BLUR); + addRow(address, postalCode); + + city = createTextField(PathogenTestDto.PRESCRIBER_CITY, PathogenTestDto.I18N_PREFIX, ValueChangeMode.BLUR); + country = createComboBox(PathogenTestDto.PRESCRIBER_COUNTRY, PathogenTestDto.I18N_PREFIX); + country.setItems(FacadeProvider.getCountryFacade().getAllActiveAsReference()); + country.setItemCaptionGenerator(CountryReferenceDto::getCaption); + addRow(city, country); + } + + private void bindFields() { + binder.forField(physicianCode).bind(PathogenTestDto::getPrescriberPhysicianCode, PathogenTestDto::setPrescriberPhysicianCode); + binder.forField(firstName).bind(PathogenTestDto::getPrescriberFirstName, PathogenTestDto::setPrescriberFirstName); + binder.forField(lastName).bind(PathogenTestDto::getPrescriberLastName, PathogenTestDto::setPrescriberLastName); + binder.forField(phone) + .withValidator(DataHelper::isValidPhoneNumber, I18nProperties.getValidationError(Validations.validPhoneNumber, phone.getCaption())) + .bind(PathogenTestDto::getPrescriberPhoneNumber, PathogenTestDto::setPrescriberPhoneNumber); + binder.forField(address).bind(PathogenTestDto::getPrescriberAddress, PathogenTestDto::setPrescriberAddress); + binder.forField(postalCode).bind(PathogenTestDto::getPrescriberPostalCode, PathogenTestDto::setPrescriberPostalCode); + binder.forField(city).bind(PathogenTestDto::getPrescriberCity, PathogenTestDto::setPrescriberCity); + binder.forField(country).bind(PathogenTestDto::getPrescriberCountry, PathogenTestDto::setPrescriberCountry); + } + + @Override + protected void updateRowAndSelfVisibility() { + super.updateRowAndSelfVisibility(); + heading.setVisible(this.isVisible()); + } + + public boolean isHeadingVisible() { + return heading.isVisible(); + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/TestIdentificationComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/TestIdentificationComponent.java new file mode 100644 index 00000000000..ff59cde046f --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/TestIdentificationComponent.java @@ -0,0 +1,94 @@ +package de.symeda.sormas.ui.samples.components; + +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.Date; + +import com.vaadin.data.Converter; +import com.vaadin.data.Result; +import com.vaadin.data.ValueContext; +import com.vaadin.shared.ui.ValueChangeMode; +import com.vaadin.ui.CheckBox; +import com.vaadin.ui.DateField; +import com.vaadin.ui.TextField; + +import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.ui.samples.events.ViaLimsChangedEvent; +import de.symeda.sormas.ui.utils.FormComponent; +import de.symeda.sormas.ui.utils.FormEventBus; + +/** + * Report date, VIA_LIMS, external ID, external order ID. + * Vaadin 8 components with own Binder. + */ +public class TestIdentificationComponent extends FormComponent { + + private static final long serialVersionUID = 1L; + + private final FormEventBus eventBus; + + private DateField reportDate; + private CheckBox viaLims; + private TextField externalId; + private TextField externalOrderId; + + public TestIdentificationComponent(FormEventBus eventBus) { + super(PathogenTestDto.class); + this.eventBus = eventBus; + buildLayout(); + bindFields(); + wireEvents(); + } + + private void buildLayout() { + reportDate = createDateField(PathogenTestDto.REPORT_DATE, PathogenTestDto.I18N_PREFIX); + viaLims = createCheckBox(PathogenTestDto.VIA_LIMS, PathogenTestDto.I18N_PREFIX); + addRow(reportDate, viaLims); + + externalId = createTextField(PathogenTestDto.EXTERNAL_ID, PathogenTestDto.I18N_PREFIX); + externalId.setValueChangeMode(ValueChangeMode.BLUR); + externalOrderId = createTextField(PathogenTestDto.EXTERNAL_ORDER_ID, PathogenTestDto.I18N_PREFIX); + externalOrderId.setValueChangeMode(ValueChangeMode.BLUR); + addRow(externalId, externalOrderId); + } + + private void bindFields() { + binder.forField(reportDate) + .withConverter(new LocalDateToDateConverter()) + .bind(PathogenTestDto::getReportDate, PathogenTestDto::setReportDate); + + binder.forField(viaLims).bind(PathogenTestDto::isViaLims, PathogenTestDto::setViaLims); + binder.forField(externalId).bind(PathogenTestDto::getExternalId, PathogenTestDto::setExternalId); + binder.forField(externalOrderId).bind(PathogenTestDto::getExternalOrderId, PathogenTestDto::setExternalOrderId); + } + + private void wireEvents() { + track(viaLims.addValueChangeListener(e -> eventBus.fire(new ViaLimsChangedEvent(Boolean.TRUE.equals(e.getValue()))))); + } + + public CheckBox getViaLimsField() { + return viaLims; + } + + /** + * Converter between Vaadin 8 LocalDate and java.util.Date used by the DTO. + */ + private static class LocalDateToDateConverter implements Converter { + + @Override + public Result convertToModel(LocalDate value, ValueContext context) { + if (value == null) { + return Result.ok(null); + } + return Result.ok(Date.from(value.atStartOfDay(ZoneId.systemDefault()).toInstant())); + } + + @Override + public LocalDate convertToPresentation(Date value, ValueContext context) { + if (value == null) { + return null; + } + return value.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + } + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/TestMethodComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/TestMethodComponent.java new file mode 100644 index 00000000000..d757063755e --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/TestMethodComponent.java @@ -0,0 +1,269 @@ +package de.symeda.sormas.ui.samples.components; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.util.Arrays; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Supplier; + +import com.vaadin.shared.ui.ValueChangeMode; +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.DateField; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.TextField; + +import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.FacadeProvider; +import de.symeda.sormas.api.i18n.I18nProperties; +import de.symeda.sormas.api.infrastructure.facility.FacilityDto; +import de.symeda.sormas.api.infrastructure.facility.FacilityReferenceDto; +import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.api.sample.PathogenTestType; +import de.symeda.sormas.api.utils.fieldaccess.UiFieldAccessCheckers; +import de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers; +import de.symeda.sormas.ui.samples.events.DiseaseChangedEvent; +import de.symeda.sormas.ui.samples.events.TestTypeChangedEvent; +import de.symeda.sormas.ui.utils.CssStyles; +import de.symeda.sormas.ui.utils.FormComponent; +import de.symeda.sormas.ui.utils.FormEventBus; + +/** + * Test type, typing ID, test date/time, lab, lab details. + * Vaadin 8 components with own Binder, self-managed visibility. + */ +public class TestMethodComponent extends FormComponent { + + private static final long serialVersionUID = 1L; + + private final FormEventBus eventBus; + private final Supplier sampleDateSupplier; + private final Map timeItems = new LinkedHashMap<>(); + + private ComboBox testTypeField; + private TextField testTypeTextField; + private Label testTypeTextSpacer; + private TextField typingIdField; + private DateField testDateField; + private ComboBox testTimeField; + private ComboBox labField; + private TextField labDetailsField; + + private PathogenTestDto currentDto; + + private HorizontalLayout typingIdRow; + private HorizontalLayout labDetailsRow; + + public TestMethodComponent(FormEventBus eventBus, Supplier sampleDateSupplier) { + super(PathogenTestDto.class); + this.eventBus = eventBus; + this.sampleDateSupplier = sampleDateSupplier; + buildLayout(); + bindFields(); + wireEvents(); + } + + private void buildLayout() { + // Test type + testTypeField = createComboBox(PathogenTestDto.TEST_TYPE, PathogenTestDto.I18N_PREFIX); + testTypeField.setItems(Arrays.asList(PathogenTestType.values())); + testTypeField.setItemCaptionGenerator(PathogenTestType::toString); + + testTypeTextField = createTextField(PathogenTestDto.TEST_TYPE_TEXT, PathogenTestDto.I18N_PREFIX, ValueChangeMode.BLUR); + testTypeTextField.setVisible(false); + + testTypeTextSpacer = createSpacer(); + addToggleRow(testTypeField, testTypeTextField, testTypeTextSpacer); + + // Typing ID + typingIdField = createTextField(PathogenTestDto.TYPING_ID, PathogenTestDto.I18N_PREFIX, ValueChangeMode.BLUR); + typingIdField.setVisible(false); + typingIdRow = addRow(typingIdField); + + // Test date/time — custom IDs so created manually and tracked + testDateField = new DateField(); + testDateField.setId(PathogenTestDto.TEST_DATE_TIME + "_date"); + testDateField.setCaption(I18nProperties.getPrefixCaption(PathogenTestDto.I18N_PREFIX, PathogenTestDto.TEST_DATE_TIME)); + CssStyles.style(testDateField, CssStyles.CAPTION_ON_TOP); + testDateField.setWidth(100, Unit.PERCENTAGE); + + testTimeField = new ComboBox<>(); + testTimeField.setId(PathogenTestDto.TEST_DATE_TIME + "_time"); + testTimeField.setCaption(""); + CssStyles.style(testTimeField, CssStyles.CAPTION_ON_TOP); + testTimeField.setWidth(100, Unit.PERCENTAGE); + testTimeField.setEmptySelectionAllowed(true); + for (int hours = 0; hours <= 23; hours++) { + for (int minutes = 0; minutes <= 59; minutes += 15) { + int totalMinutes = hours * 60 + minutes; + timeItems.put(totalMinutes, String.format("%02d:%02d", hours, minutes)); + } + } + testTimeField.setItems(timeItems.keySet()); + testTimeField.setItemCaptionGenerator(timeItems::get); + + HorizontalLayout testDateTimeGroup = new HorizontalLayout(); + testDateTimeGroup.setWidth(100, Unit.PERCENTAGE); + testDateTimeGroup.setSpacing(true); + testDateTimeGroup.addComponent(testDateField); + testDateTimeGroup.setExpandRatio(testDateField, 1); + testDateTimeGroup.addComponent(testTimeField); + testDateTimeGroup.setExpandRatio(testTimeField, 1); + + // Lab + labField = createComboBox(PathogenTestDto.LAB, PathogenTestDto.I18N_PREFIX); + labField.setItems(FacadeProvider.getFacilityFacade().getAllActiveLaboratories(true)); + labField.setItemCaptionGenerator(FacilityReferenceDto::getCaption); + + addRow(testDateTimeGroup, labField); + + // Lab details + labDetailsField = createTextField(PathogenTestDto.LAB_DETAILS, PathogenTestDto.I18N_PREFIX, ValueChangeMode.BLUR); + labDetailsField.setVisible(false); + labDetailsRow = addRowWithLeadingSpacer(labDetailsField); + } + + private void bindFields() { + binder.forField(testTypeField).asRequired().bind(PathogenTestDto::getTestType, PathogenTestDto::setTestType); + binder.forField(testTypeTextField).bind(PathogenTestDto::getTestTypeText, PathogenTestDto::setTestTypeText); + binder.forField(typingIdField).bind(PathogenTestDto::getTypingId, PathogenTestDto::setTypingId); + // testDateField and testTimeField are managed manually (both map to testDateTime) + binder.forField(labField).bind(PathogenTestDto::getLab, PathogenTestDto::setLab); + binder.forField(labDetailsField).bind(PathogenTestDto::getLabDetails, PathogenTestDto::setLabDetails); + } + + private void wireEvents() { + // Self-managed visibility: testTypeText visible for PCR_RT_PCR or OTHER + track(testTypeField.addValueChangeListener(e -> { + PathogenTestType type = e.getValue(); + + boolean showTestTypeText = type == PathogenTestType.PCR_RT_PCR || type == PathogenTestType.OTHER; + testTypeTextField.setVisible(showTestTypeText); + testTypeTextSpacer.setVisible(!showTestTypeText); + if (!showTestTypeText) { + testTypeTextField.clear(); + } + + boolean showTypingId = + type == PathogenTestType.PCR_RT_PCR || type == PathogenTestType.DNA_MICROARRAY || type == PathogenTestType.SEQUENCING; + typingIdField.setVisible(showTypingId); + if (!showTypingId) { + typingIdField.clear(); + } + updateRowVisibility(typingIdRow); + + eventBus.fire(new TestTypeChangedEvent(type)); + })); + + // Self-managed: lab details visible when "Other" lab selected + track(labField.addValueChangeListener(e -> { + FacilityReferenceDto lab = e.getValue(); + boolean showDetails = lab != null && FacilityDto.OTHER_FACILITY_UUID.equals(lab.getUuid()); + labDetailsField.setVisible(showDetails); + if (!showDetails) { + labDetailsField.clear(); + } + updateRowVisibility(labDetailsRow); + })); + + // Sync date/time fields back to DTO on value change + track(testDateField.addValueChangeListener(e -> syncTestDateTimeToDto())); + track(testTimeField.addValueChangeListener(e -> syncTestDateTimeToDto())); + + // Listen for disease changes to update test type items + track(eventBus.on(DiseaseChangedEvent.class, event -> updateTestTypeItems(event.getDisease()))); + } + + private void syncTestDateTimeToDto() { + if (currentDto == null) { + return; + } + LocalDate date = testDateField.getValue(); + if (date == null) { + currentDto.setTestDateTime(null); + return; + } + Integer totalMinutes = testTimeField.getValue(); + LocalDateTime dateTime; + if (totalMinutes != null) { + dateTime = date.atTime(totalMinutes / 60, totalMinutes % 60); + } else { + dateTime = date.atStartOfDay(); + } + currentDto.setTestDateTime(Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant())); + } + + private void populateTestDateTimeFields(PathogenTestDto dto) { + Date testDateTime = dto != null ? dto.getTestDateTime() : null; + if (testDateTime == null) { + testDateField.setValue(null); + testTimeField.setValue(null); + return; + } + LocalDateTime ldt = testDateTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); + testDateField.setValue(ldt.toLocalDate()); + LocalTime time = ldt.toLocalTime(); + int totalMinutes = time.getHour() * 60 + time.getMinute(); + if (!timeItems.containsKey(totalMinutes)) { + timeItems.put(totalMinutes, String.format("%02d:%02d", time.getHour(), time.getMinute())); + testTimeField.setItems(timeItems.keySet()); + testTimeField.setItemCaptionGenerator(timeItems::get); + } + testTimeField.setValue(totalMinutes); + } + + private void updateTestTypeItems(Disease disease) { + updateComboBoxByDisease(testTypeField, PathogenTestType.class, disease); + } + + public ComboBox getTestTypeField() { + return testTypeField; + } + + public ComboBox getLabField() { + return labField; + } + + public void setLabRequired(boolean required) { + binder.removeBinding(labField); + if (required) { + binder.forField(labField).asRequired().bind(PathogenTestDto::getLab, PathogenTestDto::setLab); + } else { + binder.forField(labField).bind(PathogenTestDto::getLab, PathogenTestDto::setLab); + } + } + + @Override + public void setDto(PathogenTestDto dto) { + this.currentDto = dto; + populateTestDateTimeFields(dto); + super.setDto(dto); + } + + @Override + public void applyVisibility(FieldVisibilityCheckers checkers, Class dtoClass) { + super.applyVisibility(checkers, dtoClass); + // testDateField/testTimeField have custom IDs that don't match the DTO property + if (!checkers.isVisible(dtoClass, PathogenTestDto.TEST_DATE_TIME)) { + testDateField.setVisible(false); + testTimeField.setVisible(false); + } + updateRowAndSelfVisibility(); + } + + @Override + public void applyAccess(UiFieldAccessCheckers checkers, Class dtoClass) { + super.applyAccess(checkers, dtoClass); + // testDateField/testTimeField have custom IDs that don't match the DTO property + if (!checkers.isAccessible(dtoClass, PathogenTestDto.TEST_DATE_TIME)) { + testDateField.setEnabled(false); + testDateField.addStyleName(CssStyles.INACCESSIBLE_FIELD); + testTimeField.setEnabled(false); + testTimeField.addStyleName(CssStyles.INACCESSIBLE_FIELD); + } + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/TestResultComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/TestResultComponent.java new file mode 100644 index 00000000000..5d43761f5c6 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/TestResultComponent.java @@ -0,0 +1,213 @@ +package de.symeda.sormas.ui.samples.components; + +import java.util.ArrayList; +import java.util.List; + +import com.vaadin.ui.CheckBox; +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.RadioButtonGroup; +import com.vaadin.ui.TextArea; + +import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.api.sample.PathogenTestResultType; +import de.symeda.sormas.api.sample.PathogenTestType; +import de.symeda.sormas.api.utils.fieldaccess.UiFieldAccessCheckers; +import de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers; +import de.symeda.sormas.ui.samples.events.DiseaseChangedEvent; +import de.symeda.sormas.ui.samples.events.SetTestResultEvent; +import de.symeda.sormas.ui.samples.events.TestResultChangedEvent; +import de.symeda.sormas.ui.samples.events.TestTypeChangedEvent; +import de.symeda.sormas.ui.samples.events.ViaLimsChangedEvent; +import de.symeda.sormas.ui.utils.CssStyles; +import de.symeda.sormas.ui.utils.FormComponent; +import de.symeda.sormas.ui.utils.FormEventBus; + +/** + * Test result, verified, preliminary, 4-fold increase, CQ/CT values, result text. + * Vaadin 8 components with own Binder, self-managed visibility. + */ +public class TestResultComponent extends FormComponent { + + private static final long serialVersionUID = 1L; + + private final FormEventBus eventBus; + private final int caseSampleCount; + private final boolean isLuxembourg; + + private final CtCqValueComponent ctCqValueComponent; + + private ComboBox testResultField; + private RadioButtonGroup testResultVerifiedField; + private RadioButtonGroup preliminaryField; + private CheckBox fourFoldIncrease; + private TextArea resultText; + + private Disease currentDisease; + private PathogenTestType currentTestType; + + public TestResultComponent(FormEventBus eventBus, int caseSampleCount, boolean isLuxembourg, Disease initialDisease) { + super(PathogenTestDto.class); + this.eventBus = eventBus; + this.caseSampleCount = caseSampleCount; + this.isLuxembourg = isLuxembourg; + this.currentDisease = initialDisease; + this.ctCqValueComponent = new CtCqValueComponent(isLuxembourg); + buildLayout(); + bindFields(); + wireEvents(); + } + + private void buildLayout() { + // Test result + testResultField = createComboBox(PathogenTestDto.TEST_RESULT, PathogenTestDto.I18N_PREFIX); + List resultTypes = new ArrayList<>(java.util.Arrays.asList(PathogenTestResultType.values())); + resultTypes.remove(PathogenTestResultType.NOT_DONE); + if (!isLuxembourg) { + resultTypes.remove(PathogenTestResultType.NOT_APPLICABLE); + } + testResultField.setItems(resultTypes); + testResultField.setItemCaptionGenerator(PathogenTestResultType::toString); + + // Test result verified (Yes/No) + testResultVerifiedField = createBooleanRadioGroup(PathogenTestDto.TEST_RESULT_VERIFIED, PathogenTestDto.I18N_PREFIX); + + // Preliminary + preliminaryField = createBooleanRadioGroup(PathogenTestDto.PRELIMINARY, PathogenTestDto.I18N_PREFIX); + preliminaryField.removeStyleName(CssStyles.CAPTION_ON_TOP); + CssStyles.style(preliminaryField, CssStyles.VSPACE_4); + + addRow( + new float[] { + 6.4f, + 4.1f, + 2 }, + testResultField, + testResultVerifiedField, + preliminaryField); + + // Four-fold increase + fourFoldIncrease = createCheckBox(PathogenTestDto.FOUR_FOLD_INCREASE_ANTIBODY_TITER, PathogenTestDto.I18N_PREFIX); + CssStyles.style(fourFoldIncrease, CssStyles.VSPACE_3, CssStyles.VSPACE_TOP_4); + fourFoldIncrease.setVisible(false); + fourFoldIncrease.setEnabled(false); + addComponent(fourFoldIncrease); + + // CT/CQ values (delegated to CtCqValueComponent) + addComponent(ctCqValueComponent); + + // Result text + resultText = createTextArea(PathogenTestDto.TEST_RESULT_TEXT, PathogenTestDto.I18N_PREFIX); + resultText.setRows(6); + addFullWidthRow(resultText); + } + + private void bindFields() { + binder.forField(testResultField).asRequired().bind(PathogenTestDto::getTestResult, PathogenTestDto::setTestResult); + binder.forField(testResultVerifiedField).bind(PathogenTestDto::getTestResultVerified, PathogenTestDto::setTestResultVerified); + binder.forField(preliminaryField).bind(PathogenTestDto::getPreliminary, PathogenTestDto::setPreliminary); + binder.forField(fourFoldIncrease).bind(PathogenTestDto::isFourFoldIncreaseAntibodyTiter, PathogenTestDto::setFourFoldIncreaseAntibodyTiter); + binder.forField(resultText).bind(PathogenTestDto::getTestResultText, PathogenTestDto::setTestResultText); + } + + private void wireEvents() { + // Test result changed -> fire event + update CQ visibility + track(testResultField.addValueChangeListener(e -> { + eventBus.fire(new TestResultChangedEvent(e.getValue())); + ctCqValueComponent.updateCqVisibility(currentDisease, currentTestType, e.getValue()); + })); + + // Listen for test type changes + track(eventBus.on(TestTypeChangedEvent.class, event -> { + PathogenTestType testType = event.getTestType(); + currentTestType = testType; + + if (testType != null) { + // Four fold increase visibility + if (testType == PathogenTestType.IGM_SERUM_ANTIBODY || testType == PathogenTestType.IGG_SERUM_ANTIBODY) { + fourFoldIncrease.setVisible(true); + fourFoldIncrease.setEnabled(caseSampleCount >= 2); + } else { + fourFoldIncrease.setVisible(false); + fourFoldIncrease.setEnabled(false); + } + + // CT fields visibility + ctCqValueComponent.updateCtVisibility(currentDisease, testType); + } else { + testResultField.clear(); + testResultField.setEnabled(true); + ctCqValueComponent.updateCtVisibility(currentDisease, null); + } + + ctCqValueComponent.updateCqVisibility(currentDisease, currentTestType, testResultField.getValue()); + })); + + // Disease sections fire this to request a specific test result value + track(eventBus.on(SetTestResultEvent.class, event -> { + if (event.getTestResult() != null) { + testResultField.setValue(event.getTestResult()); + } else { + testResultField.clear(); + } + ctCqValueComponent.updateCqVisibility(currentDisease, currentTestType, testResultField.getValue()); + })); + + // Listen for disease changes + track(eventBus.on(DiseaseChangedEvent.class, event -> { + Disease oldDisease = currentDisease; + currentDisease = event.getDisease(); + if (currentDisease != oldDisease && currentTestType != null) { + testResultField.clear(); + } + ctCqValueComponent.updateCtVisibility(currentDisease, currentTestType); + ctCqValueComponent.updateCqVisibility(currentDisease, currentTestType, testResultField.getValue()); + })); + + // VIA LIMS -> required state on test result verified + track(eventBus.on(ViaLimsChangedEvent.class, event -> setTestResultVerifiedRequired(event.isViaLims()))); + } + + public ComboBox getTestResultField() { + return testResultField; + } + + public RadioButtonGroup getTestResultVerifiedField() { + return testResultVerifiedField; + } + + private void setTestResultVerifiedRequired(boolean required) { + binder.removeBinding(testResultVerifiedField); + if (required) { + binder.forField(testResultVerifiedField) + .asRequired() + .bind(PathogenTestDto::getTestResultVerified, PathogenTestDto::setTestResultVerified); + } else { + binder.forField(testResultVerifiedField).bind(PathogenTestDto::getTestResultVerified, PathogenTestDto::setTestResultVerified); + } + } + + @Override + public void setDto(PathogenTestDto dto) { + super.setDto(dto); + ctCqValueComponent.setDto(dto); + } + + @Override + public void validate() { + super.validate(); + ctCqValueComponent.validate(); + } + + @Override + public void applyVisibility(FieldVisibilityCheckers checkers, Class dtoClass) { + super.applyVisibility(checkers, dtoClass); + ctCqValueComponent.applyVisibility(checkers, dtoClass); + } + + @Override + public void applyAccess(UiFieldAccessCheckers checkers, Class dtoClass) { + super.applyAccess(checkers, dtoClass); + ctCqValueComponent.applyAccess(checkers, dtoClass); + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/AbstractDiseaseSectionComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/AbstractDiseaseSectionComponent.java new file mode 100644 index 00000000000..6bbe016584b --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/AbstractDiseaseSectionComponent.java @@ -0,0 +1,129 @@ +package de.symeda.sormas.ui.samples.diseasesection; + +import java.util.function.Consumer; + +import com.vaadin.ui.CheckBox; +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.Component; +import com.vaadin.ui.RadioButtonGroup; +import com.vaadin.ui.TextField; +import com.vaadin.v7.data.fieldgroup.FieldGroup; + +import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.ui.utils.FormComponent; +import de.symeda.sormas.ui.utils.FormEventBus; + +/** + * Base class for disease-specific sections in the pathogen test form. + * Extends {@link FormComponent} and adds disease-section-specific lifecycle: + *

      + *
    • Deferred initialization via {@link #initialize}
    • + *
    • Mid-lifecycle {@link #cleanup()} that clears owned DTO fields before unbinding
    • + *
    • Legacy {@link FieldGroup} support for DrugSusceptibilityForm
    • + *
    • Drug susceptibility field management
    • + *
    + */ +public abstract class AbstractDiseaseSectionComponent extends FormComponent { + + private static final long serialVersionUID = 1L; + + protected FormEventBus eventBus; + protected PathogenTestFormConfig config; + protected Disease disease; + + /** Kept only for DrugSusceptibilityForm legacy binding */ + protected FieldGroup fieldGroup; + + private Component drugSusceptibilityField; + private Consumer visibilityCallback; + + protected AbstractDiseaseSectionComponent() { + super(PathogenTestDto.class); + } + + public void setVisibilityCallback(Consumer callback) { + this.visibilityCallback = callback; + } + + @Override + public void setVisible(boolean visible) { + super.setVisible(visible); + if (visibilityCallback != null) { + visibilityCallback.accept(visible); + } + } + + public void initialize(FieldGroup fieldGroup, FormEventBus eventBus, PathogenTestFormConfig config, Disease disease) { + this.fieldGroup = fieldGroup; + this.eventBus = eventBus; + this.config = config; + this.disease = disease; + + buildLayout(); + wireVisibility(); + } + + protected abstract void buildLayout(); + + protected abstract void wireVisibility(); + + /** + * Clears owned DTO fields and unbinds. Called explicitly before section swap + * to prevent stale values from being persisted when the disease changes. + */ + public void cleanup() { + removeRegistrations(); + clearOwnedFields(); + binder.removeBean(); + unbindLegacyFields(); + } + + /** + * Subclasses null out all DTO properties they own. + * Called during cleanup() while the binder still holds the bean. + */ + protected abstract void clearOwnedFields(); + + /** Override to unbind any FieldGroup-bound legacy fields (e.g. DrugSusceptibilityForm) */ + protected void unbindLegacyFields() { + } + + protected void addDrugSusceptibilityField(Component field) { + this.drugSusceptibilityField = field; + field.setVisible(false); + addComponent(field); + } + + protected void setDrugSusceptibilityRowVisible(boolean visible) { + if (drugSusceptibilityField != null) { + drugSusceptibilityField.setVisible(visible); + updateRowAndSelfVisibility(); + } + } + + @Override + protected boolean hasVisibleContent() { + return drugSusceptibilityField != null && drugSusceptibilityField.isVisible(); + } + + protected ComboBox createComboBox(String propertyId) { + return createComboBox(propertyId, PathogenTestDto.I18N_PREFIX); + } + + protected TextField createTextField(String propertyId) { + return createTextField(propertyId, PathogenTestDto.I18N_PREFIX); + } + + protected CheckBox createCheckBox(String propertyId) { + return createCheckBox(propertyId, PathogenTestDto.I18N_PREFIX); + } + + protected RadioButtonGroup createRadioButtonGroup(String propertyId) { + return createBooleanRadioGroup(propertyId, PathogenTestDto.I18N_PREFIX); + } + + protected > RadioButtonGroup createEnumRadioButtonGroup(String propertyId, Class enumClass) { + return createEnumRadioGroup(propertyId, PathogenTestDto.I18N_PREFIX, enumClass); + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CoronavirusSectionComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CoronavirusSectionComponent.java new file mode 100644 index 00000000000..3878f0a8ead --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CoronavirusSectionComponent.java @@ -0,0 +1,59 @@ +package de.symeda.sormas.ui.samples.diseasesection; + +import com.vaadin.ui.ComboBox; + +import de.symeda.sormas.api.sample.PCRTestSpecification; +import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.api.sample.PathogenTestType; +import de.symeda.sormas.ui.samples.events.SetTestResultEvent; +import de.symeda.sormas.ui.samples.events.TestTypeChangedEvent; + +public class CoronavirusSectionComponent extends AbstractDiseaseSectionComponent { + + private ComboBox pcrTestSpecification; + private PathogenTestType currentTestType; + + @Override + protected void buildLayout() { + + pcrTestSpecification = createComboBox(PathogenTestDto.PCR_TEST_SPECIFICATION); + pcrTestSpecification.setItems(PCRTestSpecification.values()); + pcrTestSpecification.setItemCaptionGenerator(PCRTestSpecification::toString); + pcrTestSpecification.setVisible(false); + addRow(pcrTestSpecification); + + binder.forField(pcrTestSpecification).bind(PathogenTestDto::getPcrTestSpecification, PathogenTestDto::setPcrTestSpecification); + } + + @Override + protected void wireVisibility() { + track(eventBus.on(TestTypeChangedEvent.class, event -> { + currentTestType = event.getTestType(); + updateVisibility(); + + // Clear test result on test type change (no auto-set for Coronavirus) + if (currentTestType != null) { + eventBus.fire(new SetTestResultEvent(null)); + } + })); + } + + @Override + protected void clearOwnedFields() { + PathogenTestDto dto = binder.getBean(); + if (dto == null) { + return; + } + dto.setPcrTestSpecification(null); + } + + private void updateVisibility() { + boolean visible = currentTestType == PathogenTestType.PCR_RT_PCR; + pcrTestSpecification.setVisible(visible); + if (!visible) { + pcrTestSpecification.clear(); + } + updateRowAndSelfVisibility(); + } + +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CryptosporidiosisSectionComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CryptosporidiosisSectionComponent.java new file mode 100644 index 00000000000..01ef35b2006 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CryptosporidiosisSectionComponent.java @@ -0,0 +1,116 @@ +package de.symeda.sormas.ui.samples.diseasesection; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.Label; +import com.vaadin.ui.TextField; + +import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.sample.GenoTypeResult; +import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.api.sample.PathogenTestResultType; +import de.symeda.sormas.api.sample.PathogenTestType; +import de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers; +import de.symeda.sormas.ui.samples.events.SetTestResultEvent; +import de.symeda.sormas.ui.samples.events.TestResultChangedEvent; +import de.symeda.sormas.ui.samples.events.TestTypeChangedEvent; + +public class CryptosporidiosisSectionComponent extends AbstractDiseaseSectionComponent { + + private ComboBox genoTypeResultField; + private TextField genoTypeResultTextField; + private Label genoTypeResultTextFieldSpacer; + + private PathogenTestType currentTestType; + private PathogenTestResultType currentResult; + + @Override + protected void buildLayout() { + + genoTypeResultField = createComboBox(PathogenTestDto.GENOTYPE_RESULT); + genoTypeResultField.setItemCaptionGenerator(GenoTypeResult::toString); + genoTypeResultField.setVisible(false); + updateGenoTypeItems(disease); + + genoTypeResultTextField = createTextField(PathogenTestDto.GENOTYPE_RESULT_TEXT); + genoTypeResultTextField.setVisible(false); + + genoTypeResultTextFieldSpacer = createSpacer(); + addToggleRow(genoTypeResultField, genoTypeResultTextField, genoTypeResultTextFieldSpacer); + + binder.forField(genoTypeResultField).bind(PathogenTestDto::getGenoTypeResult, PathogenTestDto::setGenoTypeResult); + binder.forField(genoTypeResultTextField).bind(PathogenTestDto::getGenoTypeResultText, PathogenTestDto::setGenoTypeResultText); + } + + @Override + protected void wireVisibility() { + genoTypeResultField.addValueChangeListener(e -> { + boolean showText = e.getValue() == GenoTypeResult.OTHER && genoTypeResultField.isVisible(); + genoTypeResultTextField.setVisible(showText); + genoTypeResultTextFieldSpacer.setVisible(!showText); + if (!showText) { + genoTypeResultTextField.clear(); + } + }); + + track(eventBus.on(TestTypeChangedEvent.class, event -> { + currentTestType = event.getTestType(); + updateVisibility(); + updateGenoTypeItems(disease); + + // Auto-set test result + if (currentTestType == PathogenTestType.GENOTYPING) { + eventBus.fire(new SetTestResultEvent(PathogenTestResultType.POSITIVE)); + } else if (currentTestType != null) { + eventBus.fire(new SetTestResultEvent(null)); + } + })); + + track(eventBus.on(TestResultChangedEvent.class, event -> { + currentResult = event.getTestResult(); + updateVisibility(); + })); + + } + + private void updateVisibility() { + boolean visible = currentTestType == PathogenTestType.GENOTYPING && currentResult == PathogenTestResultType.POSITIVE; + genoTypeResultField.setVisible(visible); + if (!visible) { + genoTypeResultField.clear(); + genoTypeResultTextField.setVisible(false); + genoTypeResultTextField.clear(); + } + updateRowAndSelfVisibility(); + } + + @Override + protected void clearOwnedFields() { + PathogenTestDto dto = binder.getBean(); + if (dto == null) { + return; + } + dto.setGenoTypeResult(null); + dto.setGenoTypeResultText(null); + } + + private void updateGenoTypeItems(Disease disease) { + List filtered = Arrays.stream(GenoTypeResult.values()).filter(value -> { + try { + java.lang.reflect.Field enumField = GenoTypeResult.class.getField(value.name()); + return FieldVisibilityCheckers.withDisease(disease).isVisible(GenoTypeResult.class, enumField); + } catch (NoSuchFieldException e) { + return true; + } + }).collect(Collectors.toList()); + GenoTypeResult current = genoTypeResultField.getValue(); + genoTypeResultField.setItems(filtered); + if (current != null && filtered.contains(current)) { + genoTypeResultField.setValue(current); + } + } + +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CsmSectionComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CsmSectionComponent.java new file mode 100644 index 00000000000..927a3091a4d --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CsmSectionComponent.java @@ -0,0 +1,61 @@ +package de.symeda.sormas.ui.samples.diseasesection; + +import com.vaadin.ui.TextField; + +import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.api.sample.PathogenTestResultType; +import de.symeda.sormas.api.sample.PathogenTestType; +import de.symeda.sormas.ui.samples.events.SetTestResultEvent; +import de.symeda.sormas.ui.samples.events.TestResultChangedEvent; +import de.symeda.sormas.ui.samples.events.TestTypeChangedEvent; + +public class CsmSectionComponent extends AbstractDiseaseSectionComponent { + + private TextField serotypeField; + private PathogenTestResultType currentResult; + + @Override + protected void buildLayout() { + serotypeField = createTextField(PathogenTestDto.SEROTYPE); + serotypeField.setVisible(false); + addRow(serotypeField, createSpacer()); + + binder.forField(serotypeField).bind(PathogenTestDto::getSerotype, PathogenTestDto::setSerotype); + } + + @Override + protected void wireVisibility() { + track(eventBus.on(TestTypeChangedEvent.class, event -> { + PathogenTestType testType = event.getTestType(); + + // Clear test result on test type change (no auto-set for CSM) + if (testType != null) { + eventBus.fire(new SetTestResultEvent(null)); + } + })); + + track(eventBus.on(TestResultChangedEvent.class, event -> { + currentResult = event.getTestResult(); + updateVisibility(); + })); + } + + @Override + protected void clearOwnedFields() { + PathogenTestDto dto = binder.getBean(); + if (dto == null) { + return; + } + dto.setSerotype(null); + } + + private void updateVisibility() { + boolean visible = currentResult == PathogenTestResultType.POSITIVE; + serotypeField.setVisible(visible); + if (!visible) { + serotypeField.clear(); + } + updateRowAndSelfVisibility(); + } + +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/DefaultSectionComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/DefaultSectionComponent.java new file mode 100644 index 00000000000..2e0b8693a49 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/DefaultSectionComponent.java @@ -0,0 +1,56 @@ +package de.symeda.sormas.ui.samples.diseasesection; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.sample.PathogenTestResultType; +import de.symeda.sormas.api.sample.PathogenTestType; +import de.symeda.sormas.ui.samples.events.SetTestResultEvent; +import de.symeda.sormas.ui.samples.events.TestTypeChangedEvent; + +/** + * Disease section for diseases with no extra fields. + * Handles auto-set test result for diseases that don't have a dedicated section component. + */ +public class DefaultSectionComponent extends AbstractDiseaseSectionComponent { + + private static final Map> AUTO_POSITIVE_TYPES = buildAutoPositiveTypes(); + + private static Map> buildAutoPositiveTypes() { + Map> map = new HashMap<>(); + map.put( + Disease.RESPIRATORY_SYNCYTIAL_VIRUS, + Collections.unmodifiableList(Arrays.asList(PathogenTestType.SEQUENCING, PathogenTestType.WHOLE_GENOME_SEQUENCING))); + map.put(Disease.INFLUENZA, Collections.singletonList(PathogenTestType.ISOLATION)); + return Collections.unmodifiableMap(map); + } + + @Override + protected void buildLayout() { + } + + @Override + protected void wireVisibility() { + track(eventBus.on(TestTypeChangedEvent.class, event -> { + PathogenTestType testType = event.getTestType(); + if (testType == null) { + return; + } + + List positiveTypes = AUTO_POSITIVE_TYPES.get(disease); + if (positiveTypes != null && positiveTypes.contains(testType)) { + eventBus.fire(new SetTestResultEvent(PathogenTestResultType.POSITIVE)); + } else { + eventBus.fire(new SetTestResultEvent(null)); + } + })); + } + + @Override + protected void clearOwnedFields() { + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/DiseaseSectionFactory.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/DiseaseSectionFactory.java new file mode 100644 index 00000000000..726e00610a4 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/DiseaseSectionFactory.java @@ -0,0 +1,38 @@ +package de.symeda.sormas.ui.samples.diseasesection; + +import de.symeda.sormas.api.Disease; + +/** + * Factory for component-based disease sections. + * Replaces {@link DiseaseSectionLayout#forDisease(Disease)}. + */ +public final class DiseaseSectionFactory { + + private DiseaseSectionFactory() { + } + + public static AbstractDiseaseSectionComponent forDisease(Disease disease) { + if (disease == null) { + return new DefaultSectionComponent(); + } + switch (disease) { + case TUBERCULOSIS: + case LATENT_TUBERCULOSIS: + return new TuberculosisSectionComponent(); + case MEASLES: + return new MeaslesSectionComponent(); + case CRYPTOSPORIDIOSIS: + return new CryptosporidiosisSectionComponent(); + case INVASIVE_MENINGOCOCCAL_INFECTION: + return new ImiSectionComponent(); + case INVASIVE_PNEUMOCOCCAL_INFECTION: + return new IpiSectionComponent(); + case CSM: + return new CsmSectionComponent(); + case CORONAVIRUS: + return new CoronavirusSectionComponent(); + default: + return new DefaultSectionComponent(); + } + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/ImiSectionComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/ImiSectionComponent.java new file mode 100644 index 00000000000..99827633cbd --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/ImiSectionComponent.java @@ -0,0 +1,139 @@ +package de.symeda.sormas.ui.samples.diseasesection; + +import java.util.Arrays; +import java.util.List; + +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.Label; +import com.vaadin.ui.TextField; + +import de.symeda.sormas.api.FacadeProvider; +import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.api.sample.PathogenTestResultType; +import de.symeda.sormas.api.sample.PathogenTestType; +import de.symeda.sormas.api.sample.SeroGroupSpecification; +import de.symeda.sormas.api.utils.fieldaccess.UiFieldAccessCheckers; +import de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers; +import de.symeda.sormas.ui.samples.events.SetTestResultEvent; +import de.symeda.sormas.ui.samples.events.TestResultChangedEvent; +import de.symeda.sormas.ui.samples.events.TestTypeChangedEvent; +import de.symeda.sormas.ui.therapy.DrugSusceptibilityForm; + +public class ImiSectionComponent extends AbstractDiseaseSectionComponent { + + private static final List IMI_TEST_TYPES = Arrays.asList( + PathogenTestType.SEROGROUPING, + PathogenTestType.MULTILOCUS_SEQUENCE_TYPING, + PathogenTestType.SLIDE_AGGLUTINATION, + PathogenTestType.WHOLE_GENOME_SEQUENCING); + + private static final List AUTO_POSITIVE_TYPES = Arrays.asList( + PathogenTestType.SEROGROUPING, + PathogenTestType.MULTILOCUS_SEQUENCE_TYPING, + PathogenTestType.SLIDE_AGGLUTINATION, + PathogenTestType.WHOLE_GENOME_SEQUENCING, + PathogenTestType.SEQUENCING, + PathogenTestType.ANTIBIOTIC_SUSCEPTIBILITY); + + private ComboBox seroGroupSpecField; + private TextField seroGroupSpecTextField; + private Label seroGroupSpecTextSpacer; + private DrugSusceptibilityForm drugSusceptibilityField; + + private PathogenTestType currentTestType; + private PathogenTestResultType currentResult; + + @Override + protected void buildLayout() { + + seroGroupSpecField = createComboBox(PathogenTestDto.SERO_GROUP_SPECIFICATION); + seroGroupSpecField.setItems(SeroGroupSpecification.values()); + seroGroupSpecField.setItemCaptionGenerator(SeroGroupSpecification::toString); + seroGroupSpecField.setVisible(false); + + seroGroupSpecTextField = createTextField(PathogenTestDto.SERO_GROUP_SPECIFICATION_TEXT); + seroGroupSpecTextField.setVisible(false); + + seroGroupSpecTextSpacer = createSpacer(); + addToggleRow(seroGroupSpecField, seroGroupSpecTextField, seroGroupSpecTextSpacer); + + binder.forField(seroGroupSpecField).bind(PathogenTestDto::getSeroGroupSpecification, PathogenTestDto::setSeroGroupSpecification); + binder.forField(seroGroupSpecTextField).bind(PathogenTestDto::getSeroGroupSpecificationText, PathogenTestDto::setSeroGroupSpecificationText); + + // DrugSusceptibilityForm — legacy v7, bound via parent FieldGroup + drugSusceptibilityField = new DrugSusceptibilityForm( + FieldVisibilityCheckers.getNoop(), + UiFieldAccessCheckers.getDefault(true, FacadeProvider.getConfigFacade().getCountryLocale())); + drugSusceptibilityField.setCaption(null); + fieldGroup.bind(drugSusceptibilityField, PathogenTestDto.DRUG_SUSCEPTIBILITY); + addDrugSusceptibilityField(drugSusceptibilityField); + } + + @Override + protected void wireVisibility() { + // Self-managed: seroGroupSpecText visible only when OTHER + seroGroupSpecField.addValueChangeListener(e -> { + boolean showText = e.getValue() == SeroGroupSpecification.OTHER; + seroGroupSpecTextField.setVisible(showText); + seroGroupSpecTextSpacer.setVisible(!showText); + if (!showText) { + seroGroupSpecTextField.clear(); + } + }); + + track(eventBus.on(TestTypeChangedEvent.class, event -> { + currentTestType = event.getTestType(); + updateVisibility(); + + // Drug susceptibility visibility + if (drugSusceptibilityField != null) { + boolean visible = drugSusceptibilityField.updateFieldsVisibility(disease, currentTestType); + setDrugSusceptibilityRowVisible(visible); + } + + // Auto-set test result (applies to all countries; Luxembourg-only guard + // on ANTIBIOTIC_SUSCEPTIBILITY was previously redundant with this list) + if (currentTestType != null && AUTO_POSITIVE_TYPES.contains(currentTestType)) { + eventBus.fire(new SetTestResultEvent(PathogenTestResultType.POSITIVE)); + } else if (currentTestType != null) { + eventBus.fire(new SetTestResultEvent(null)); + } + })); + + track(eventBus.on(TestResultChangedEvent.class, event -> { + currentResult = event.getTestResult(); + updateVisibility(); + })); + + } + + private void updateVisibility() { + boolean visible = currentResult == PathogenTestResultType.POSITIVE && IMI_TEST_TYPES.contains(currentTestType); + seroGroupSpecField.setVisible(visible); + if (!visible) { + seroGroupSpecField.clear(); + seroGroupSpecTextField.setVisible(false); + seroGroupSpecTextField.clear(); + } + updateRowAndSelfVisibility(); + } + + @Override + protected void clearOwnedFields() { + PathogenTestDto dto = binder.getBean(); + if (dto == null) { + return; + } + dto.setSeroGroupSpecification(null); + dto.setSeroGroupSpecificationText(null); + } + + @Override + protected void unbindLegacyFields() { + if (drugSusceptibilityField != null) { + fieldGroup.unbind(drugSusceptibilityField); + drugSusceptibilityField = null; + } + } + +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/IpiSectionComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/IpiSectionComponent.java new file mode 100644 index 00000000000..a0dc26323a9 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/IpiSectionComponent.java @@ -0,0 +1,151 @@ +package de.symeda.sormas.ui.samples.diseasesection; + +import java.util.Arrays; +import java.util.List; + +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.TextField; + +import de.symeda.sormas.api.FacadeProvider; +import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.api.sample.PathogenTestResultType; +import de.symeda.sormas.api.sample.PathogenTestType; +import de.symeda.sormas.api.sample.SerotypingMethod; +import de.symeda.sormas.api.utils.fieldaccess.UiFieldAccessCheckers; +import de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers; +import de.symeda.sormas.ui.samples.events.SetTestResultEvent; +import de.symeda.sormas.ui.samples.events.TestResultChangedEvent; +import de.symeda.sormas.ui.samples.events.TestTypeChangedEvent; +import de.symeda.sormas.ui.therapy.DrugSusceptibilityForm; + +public class IpiSectionComponent extends AbstractDiseaseSectionComponent { + + private static final List SEROTYPE_EXTENDED_TYPES = Arrays.asList( + PathogenTestType.WHOLE_GENOME_SEQUENCING, + PathogenTestType.SLIDE_AGGLUTINATION, + PathogenTestType.MULTILOCUS_SEQUENCE_TYPING, + PathogenTestType.SEROGROUPING); + + private static final List AUTO_POSITIVE_TYPES = Arrays.asList( + PathogenTestType.SEROGROUPING, + PathogenTestType.MULTILOCUS_SEQUENCE_TYPING, + PathogenTestType.SLIDE_AGGLUTINATION, + PathogenTestType.WHOLE_GENOME_SEQUENCING, + PathogenTestType.SEQUENCING, + PathogenTestType.ANTIBIOTIC_SUSCEPTIBILITY); + + private TextField serotypeField; + private ComboBox serotypingMethodField; + private TextField serotypingMethodTextField; + private DrugSusceptibilityForm drugSusceptibilityField; + + private PathogenTestType currentTestType; + private PathogenTestResultType currentResult; + + @Override + protected void buildLayout() { + + serotypeField = createTextField(PathogenTestDto.SEROTYPE); + serotypeField.setVisible(false); + + serotypingMethodField = createComboBox(PathogenTestDto.SEROTYPING_METHOD); + serotypingMethodField.setItems(SerotypingMethod.values()); + serotypingMethodField.setItemCaptionGenerator(SerotypingMethod::toString); + serotypingMethodField.setVisible(false); + + addRow(serotypeField, serotypingMethodField); + + serotypingMethodTextField = createTextField(PathogenTestDto.SERO_TYPING_METHOD_TEXT); + serotypingMethodTextField.setVisible(false); + addRow(serotypingMethodTextField); + + binder.forField(serotypeField).bind(PathogenTestDto::getSerotype, PathogenTestDto::setSerotype); + binder.forField(serotypingMethodField).bind(PathogenTestDto::getSeroTypingMethod, PathogenTestDto::setSeroTypingMethod); + binder.forField(serotypingMethodTextField).bind(PathogenTestDto::getSeroTypingMethodText, PathogenTestDto::setSeroTypingMethodText); + + // DrugSusceptibilityForm — legacy v7, bound via parent FieldGroup + drugSusceptibilityField = new DrugSusceptibilityForm( + FieldVisibilityCheckers.getNoop(), + UiFieldAccessCheckers.getDefault(true, FacadeProvider.getConfigFacade().getCountryLocale())); + drugSusceptibilityField.setCaption(null); + fieldGroup.bind(drugSusceptibilityField, PathogenTestDto.DRUG_SUSCEPTIBILITY); + addDrugSusceptibilityField(drugSusceptibilityField); + } + + @Override + protected void wireVisibility() { + // Self-managed: serotypingMethodText visible only when OTHER + serotypingMethodField.addValueChangeListener(e -> { + boolean showText = e.getValue() == SerotypingMethod.OTHER; + serotypingMethodTextField.setVisible(showText); + if (!showText) { + serotypingMethodTextField.clear(); + } + }); + + track(eventBus.on(TestTypeChangedEvent.class, event -> { + currentTestType = event.getTestType(); + updateVisibility(); + + // Drug susceptibility visibility + if (drugSusceptibilityField != null) { + boolean visible = drugSusceptibilityField.updateFieldsVisibility(disease, currentTestType); + setDrugSusceptibilityRowVisible(visible); + } + + // Auto-set test result (applies to all countries; Luxembourg-only guard + // on ANTIBIOTIC_SUSCEPTIBILITY was previously redundant with this list) + if (currentTestType != null && AUTO_POSITIVE_TYPES.contains(currentTestType)) { + eventBus.fire(new SetTestResultEvent(PathogenTestResultType.POSITIVE)); + } else if (currentTestType != null) { + eventBus.fire(new SetTestResultEvent(null)); + } + })); + + track(eventBus.on(TestResultChangedEvent.class, event -> { + currentResult = event.getTestResult(); + updateVisibility(); + })); + + } + + private void updateVisibility() { + boolean isPositive = currentResult == PathogenTestResultType.POSITIVE; + + // Serotype visible on extended test types + positive + boolean showSerotype = isPositive && SEROTYPE_EXTENDED_TYPES.contains(currentTestType); + serotypeField.setVisible(showSerotype); + if (!showSerotype) { + serotypeField.clear(); + } + + // Serotyping method visible on SEROGROUPING + positive + boolean showMethod = isPositive && currentTestType == PathogenTestType.SEROGROUPING; + serotypingMethodField.setVisible(showMethod); + if (!showMethod) { + serotypingMethodField.clear(); + serotypingMethodTextField.setVisible(false); + serotypingMethodTextField.clear(); + } + updateRowAndSelfVisibility(); + } + + @Override + protected void clearOwnedFields() { + PathogenTestDto dto = binder.getBean(); + if (dto == null) { + return; + } + dto.setSerotype(null); + dto.setSeroTypingMethod(null); + dto.setSeroTypingMethodText(null); + } + + @Override + protected void unbindLegacyFields() { + if (drugSusceptibilityField != null) { + fieldGroup.unbind(drugSusceptibilityField); + drugSusceptibilityField = null; + } + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/MeaslesSectionComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/MeaslesSectionComponent.java new file mode 100644 index 00000000000..f8d6b679c58 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/MeaslesSectionComponent.java @@ -0,0 +1,99 @@ +package de.symeda.sormas.ui.samples.diseasesection; + +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.Label; +import com.vaadin.ui.TextField; + +import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.sample.GenoTypeResult; +import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.api.sample.PathogenTestResultType; +import de.symeda.sormas.api.sample.PathogenTestType; +import de.symeda.sormas.ui.samples.events.SetTestResultEvent; +import de.symeda.sormas.ui.samples.events.TestResultChangedEvent; +import de.symeda.sormas.ui.samples.events.TestTypeChangedEvent; + +public class MeaslesSectionComponent extends AbstractDiseaseSectionComponent { + + private ComboBox genoTypeResultField; + private TextField genoTypeResultTextField; + private Label genoTypeResultTextSpacer; + + private PathogenTestType currentTestType; + private PathogenTestResultType currentResult; + + @Override + protected void buildLayout() { + genoTypeResultField = createComboBox(PathogenTestDto.GENOTYPE_RESULT); + genoTypeResultField.setItemCaptionGenerator(GenoTypeResult::toString); + genoTypeResultField.setVisible(false); + updateGenoTypeItems(disease); + + genoTypeResultTextField = createTextField(PathogenTestDto.GENOTYPE_RESULT_TEXT); + genoTypeResultTextField.setVisible(false); + + genoTypeResultTextSpacer = createSpacer(); + addToggleRow(genoTypeResultField, genoTypeResultTextField, genoTypeResultTextSpacer); + + binder.forField(genoTypeResultField).bind(PathogenTestDto::getGenoTypeResult, PathogenTestDto::setGenoTypeResult); + binder.forField(genoTypeResultTextField).bind(PathogenTestDto::getGenoTypeResultText, PathogenTestDto::setGenoTypeResultText); + } + + @Override + protected void wireVisibility() { + // Self-managed: genoTypeResultText visible only when OTHER selected + genoTypeResultField.addValueChangeListener(e -> { + boolean showText = e.getValue() == GenoTypeResult.OTHER && genoTypeResultField.isVisible(); + genoTypeResultTextField.setVisible(showText); + genoTypeResultTextSpacer.setVisible(!showText); + if (!showText) { + genoTypeResultTextField.clear(); + } + }); + + track(eventBus.on(TestTypeChangedEvent.class, event -> { + currentTestType = event.getTestType(); + updateVisibility(); + updateGenoTypeItems(disease); + + // Auto-set test result + if (currentTestType == PathogenTestType.GENOTYPING) { + eventBus.fire(new SetTestResultEvent(PathogenTestResultType.POSITIVE)); + } else if (currentTestType != null) { + eventBus.fire(new SetTestResultEvent(null)); + } + })); + + track(eventBus.on(TestResultChangedEvent.class, event -> { + currentResult = event.getTestResult(); + updateVisibility(); + })); + + } + + private void updateVisibility() { + boolean visible = currentTestType == PathogenTestType.GENOTYPING && currentResult == PathogenTestResultType.POSITIVE; + genoTypeResultField.setVisible(visible); + if (!visible) { + genoTypeResultField.clear(); + genoTypeResultTextField.setVisible(false); + genoTypeResultTextField.clear(); + } + updateRowAndSelfVisibility(); + } + + @Override + protected void clearOwnedFields() { + PathogenTestDto dto = binder.getBean(); + if (dto == null) { + return; + } + dto.setGenoTypeResult(null); + dto.setGenoTypeResultText(null); + } + + private void updateGenoTypeItems(Disease disease) { + updateComboBoxByDisease(genoTypeResultField, GenoTypeResult.class, disease); + } + +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/TuberculosisSectionComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/TuberculosisSectionComponent.java new file mode 100644 index 00000000000..c26568351af --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/TuberculosisSectionComponent.java @@ -0,0 +1,345 @@ +package de.symeda.sormas.ui.samples.diseasesection; + +import com.vaadin.data.ValueProvider; +import com.vaadin.server.Setter; +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.RadioButtonGroup; +import com.vaadin.ui.TextField; + +import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.FacadeProvider; +import de.symeda.sormas.api.sample.PathogenSpecie; +import de.symeda.sormas.api.sample.PathogenStrainCallStatus; +import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.api.sample.PathogenTestResultType; +import de.symeda.sormas.api.sample.PathogenTestScale; +import de.symeda.sormas.api.sample.PathogenTestType; +import de.symeda.sormas.api.utils.YesNoUnknown; +import de.symeda.sormas.api.utils.fieldaccess.UiFieldAccessCheckers; +import de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers; +import de.symeda.sormas.ui.samples.events.DiseaseChangedEvent; +import de.symeda.sormas.ui.samples.events.SetTestResultEvent; +import de.symeda.sormas.ui.samples.events.TestResultChangedEvent; +import de.symeda.sormas.ui.samples.events.TestTypeChangedEvent; +import de.symeda.sormas.ui.therapy.DrugSusceptibilityForm; +import de.symeda.sormas.ui.utils.StringToFloatNullableConverter; + +public class TuberculosisSectionComponent extends AbstractDiseaseSectionComponent { + + private RadioButtonGroup rifampicinResistant; + private RadioButtonGroup isoniazidResistant; + private ComboBox testScale; + private ComboBox strainCallStatus; + private ComboBox specie; + private TextField patternProfile; + private DrugSusceptibilityForm drugSusceptibilityField; + + // Tube fields + private TextField tubeNil; + private RadioButtonGroup tubeNilGT10; + private TextField tubeAgTb1; + private RadioButtonGroup tubeAgTb1GT10; + private TextField tubeAgTb2; + private RadioButtonGroup tubeAgTb2GT10; + private TextField tubeMitogene; + private RadioButtonGroup tubeMitogeneGT10; + + private PathogenTestType currentTestType; + private PathogenTestResultType currentResult; + + @Override + protected void buildLayout() { + + // Rifampicin / Isoniazid resistant + rifampicinResistant = createEnumRadioButtonGroup(PathogenTestDto.RIFAMPICIN_RESISTANT, YesNoUnknown.class); + rifampicinResistant.setVisible(false); + + isoniazidResistant = createEnumRadioButtonGroup(PathogenTestDto.ISONIAZID_RESISTANT, YesNoUnknown.class); + isoniazidResistant.setVisible(false); + + addRow(rifampicinResistant, isoniazidResistant); + + // Test scale + testScale = createComboBox(PathogenTestDto.TEST_SCALE); + testScale.setItems(PathogenTestScale.values()); + testScale.setItemCaptionGenerator(PathogenTestScale::toString); + testScale.setVisible(false); + addRow(testScale); + + // Strain call status + strainCallStatus = createComboBox(PathogenTestDto.STRAIN_CALL_STATUS); + strainCallStatus.setItemCaptionGenerator(PathogenStrainCallStatus::toString); + strainCallStatus.setVisible(false); + updateStrainCallStatusItems(disease); + addRow(strainCallStatus); + + // Specie + specie = createComboBox(PathogenTestDto.SPECIE); + specie.setItems(PathogenSpecie.values()); + specie.setItemCaptionGenerator(PathogenSpecie::toString); + specie.setVisible(false); + addRow(specie); + + // Pattern profile + patternProfile = createTextField(PathogenTestDto.PATTERN_PROFILE); + patternProfile.setVisible(false); + addRow(patternProfile); + + // Bind non-tube fields + binder.forField(rifampicinResistant).bind(PathogenTestDto::getRifampicinResistant, PathogenTestDto::setRifampicinResistant); + binder.forField(isoniazidResistant).bind(PathogenTestDto::getIsoniazidResistant, PathogenTestDto::setIsoniazidResistant); + binder.forField(testScale).bind(PathogenTestDto::getTestScale, PathogenTestDto::setTestScale); + binder.forField(strainCallStatus).bind(PathogenTestDto::getStrainCallStatus, PathogenTestDto::setStrainCallStatus); + binder.forField(specie).bind(PathogenTestDto::getSpecie, PathogenTestDto::setSpecie); + binder.forField(patternProfile).bind(PathogenTestDto::getPatternProfile, PathogenTestDto::setPatternProfile); + + // DrugSusceptibilityForm — legacy v7 + drugSusceptibilityField = new DrugSusceptibilityForm( + FieldVisibilityCheckers.getNoop(), + UiFieldAccessCheckers.getDefault(true, FacadeProvider.getConfigFacade().getCountryLocale())); + drugSusceptibilityField.setCaption(null); + fieldGroup.bind(drugSusceptibilityField, PathogenTestDto.DRUG_SUSCEPTIBILITY); + addDrugSusceptibilityField(drugSusceptibilityField); + + // Tube fields + buildTubeFields(); + } + + private void buildTubeFields() { + tubeNil = createTubeTextField(PathogenTestDto.TUBE_NIL); + tubeNilGT10 = createRadioButtonGroup(PathogenTestDto.TUBE_NIL_GT10); + tubeNilGT10.setVisible(false); + tubeNil.setVisible(false); + addRow(tubeNil, tubeNilGT10); + + tubeAgTb1 = createTubeTextField(PathogenTestDto.TUBE_AG_TB1); + tubeAgTb1GT10 = createRadioButtonGroup(PathogenTestDto.TUBE_AG_TB1_GT10); + tubeAgTb1GT10.setVisible(false); + tubeAgTb1.setVisible(false); + addRow(tubeAgTb1, tubeAgTb1GT10); + + tubeAgTb2 = createTubeTextField(PathogenTestDto.TUBE_AG_TB2); + tubeAgTb2GT10 = createRadioButtonGroup(PathogenTestDto.TUBE_AG_TB2_GT10); + tubeAgTb2GT10.setVisible(false); + tubeAgTb2.setVisible(false); + addRow(tubeAgTb2, tubeAgTb2GT10); + + tubeMitogene = createTubeTextField(PathogenTestDto.TUBE_MITOGENE); + tubeMitogeneGT10 = createRadioButtonGroup(PathogenTestDto.TUBE_MITOGENE_GT10); + tubeMitogeneGT10.setVisible(false); + tubeMitogene.setVisible(false); + addRow(tubeMitogene, tubeMitogeneGT10); + + // Bind and sync tube fields + bindTubePair( + tubeNil, + PathogenTestDto::getTubeNil, + PathogenTestDto::setTubeNil, + tubeNilGT10, + PathogenTestDto::getTubeNilGT10, + PathogenTestDto::setTubeNilGT10); + bindTubePair( + tubeAgTb1, + PathogenTestDto::getTubeAgTb1, + PathogenTestDto::setTubeAgTb1, + tubeAgTb1GT10, + PathogenTestDto::getTubeAgTb1GT10, + PathogenTestDto::setTubeAgTb1GT10); + bindTubePair( + tubeAgTb2, + PathogenTestDto::getTubeAgTb2, + PathogenTestDto::setTubeAgTb2, + tubeAgTb2GT10, + PathogenTestDto::getTubeAgTb2GT10, + PathogenTestDto::setTubeAgTb2GT10); + bindTubePair( + tubeMitogene, + PathogenTestDto::getTubeMitogene, + PathogenTestDto::setTubeMitogene, + tubeMitogeneGT10, + PathogenTestDto::getTubeMitogeneGT10, + PathogenTestDto::setTubeMitogeneGT10); + } + + private TextField createTubeTextField(String propertyId) { + return createTextField(propertyId); + } + + @Override + protected void wireVisibility() { + // Drug susceptibility, tube fields, and auto-set apply regardless of Luxembourg + track(eventBus.on(TestTypeChangedEvent.class, event -> { + currentTestType = event.getTestType(); + + if (config.isLuxembourg) { + updateFieldVisibility(); + } + + updateDrugSusceptibility(currentTestType); + setTubeFieldsVisible(currentTestType); + + // Auto-set test result (Luxembourg only) + if (currentTestType != null && config.isLuxembourg) { + if (currentTestType == PathogenTestType.BEIJINGGENOTYPING + || currentTestType == PathogenTestType.MIRU_PATTERN_CODE + || currentTestType == PathogenTestType.ANTIBIOTIC_SUSCEPTIBILITY) { + eventBus.fire(new SetTestResultEvent(PathogenTestResultType.NOT_APPLICABLE)); + } else if (currentTestType == PathogenTestType.SPOLIGOTYPING) { + eventBus.fire(new SetTestResultEvent(PathogenTestResultType.POSITIVE)); + } else { + eventBus.fire(new SetTestResultEvent(null)); + } + } else if (currentTestType != null) { + eventBus.fire(new SetTestResultEvent(null)); + } + })); + + track(eventBus.on(TestResultChangedEvent.class, event -> { + currentResult = event.getTestResult(); + if (config.isLuxembourg) { + updateFieldVisibility(); + } + })); + + track(eventBus.on(DiseaseChangedEvent.class, event -> { + disease = event.getDisease(); + if (config.isLuxembourg) { + updateFieldVisibility(); + updateStrainCallStatusItems(disease); + } + setTubeFieldsVisible(currentTestType); + updateDrugSusceptibility(currentTestType); + })); + } + + private void updateFieldVisibility() { + // Rifampicin resistant: PCR_RT_PCR + POSITIVE + boolean showRifampicin = currentTestType == PathogenTestType.PCR_RT_PCR && currentResult == PathogenTestResultType.POSITIVE; + rifampicinResistant.setVisible(showRifampicin); + if (!showRifampicin) { + rifampicinResistant.clear(); + } + + // Isoniazid follows same conditions as rifampicin + isoniazidResistant.setVisible(showRifampicin); + if (!showRifampicin) { + isoniazidResistant.clear(); + } + + // Test scale: MICROSCOPY + boolean showTestScale = currentTestType == PathogenTestType.MICROSCOPY; + testScale.setVisible(showTestScale); + if (!showTestScale) { + testScale.clear(); + } + + // Strain call status: BEIJINGGENOTYPING + boolean showStrain = currentTestType == PathogenTestType.BEIJINGGENOTYPING; + strainCallStatus.setVisible(showStrain); + if (!showStrain) { + strainCallStatus.clear(); + } + + // Specie: SPOLIGOTYPING + POSITIVE + boolean showSpecie = currentTestType == PathogenTestType.SPOLIGOTYPING && currentResult == PathogenTestResultType.POSITIVE; + specie.setVisible(showSpecie); + if (!showSpecie) { + specie.clear(); + } + + // Pattern profile: MIRU_PATTERN_CODE + boolean showPattern = currentTestType == PathogenTestType.MIRU_PATTERN_CODE; + patternProfile.setVisible(showPattern); + if (!showPattern) { + patternProfile.clear(); + } + updateRowAndSelfVisibility(); + } + + private void setTubeFieldsVisible(PathogenTestType testType) { + boolean showTubes = testType == PathogenTestType.IGRA && config.isLuxembourg; + TextField[] numericFields = { + tubeNil, + tubeAgTb1, + tubeAgTb2, + tubeMitogene }; + @SuppressWarnings("unchecked") + RadioButtonGroup[] gt10Fields = new RadioButtonGroup[] { + tubeNilGT10, + tubeAgTb1GT10, + tubeAgTb2GT10, + tubeMitogeneGT10 }; + + for (int i = 0; i < numericFields.length; i++) { + numericFields[i].setVisible(showTubes); + gt10Fields[i].setVisible(showTubes); + if (!showTubes) { + numericFields[i].clear(); + gt10Fields[i].clear(); + } + } + updateRowAndSelfVisibility(); + } + + private void updateStrainCallStatusItems(Disease disease) { + updateComboBoxByDisease(strainCallStatus, PathogenStrainCallStatus.class, disease); + } + + private void updateDrugSusceptibility(PathogenTestType testType) { + if (drugSusceptibilityField != null) { + boolean visible = drugSusceptibilityField.updateFieldsVisibility(disease, testType); + setDrugSusceptibilityRowVisible(visible); + } + } + + @Override + protected void clearOwnedFields() { + PathogenTestDto dto = binder.getBean(); + if (dto == null) { + return; + } + dto.setRifampicinResistant(null); + dto.setIsoniazidResistant(null); + dto.setTestScale(null); + dto.setStrainCallStatus(null); + dto.setSpecie(null); + dto.setPatternProfile(null); + dto.setTubeNil(null); + dto.setTubeNilGT10(null); + dto.setTubeAgTb1(null); + dto.setTubeAgTb1GT10(null); + dto.setTubeAgTb2(null); + dto.setTubeAgTb2GT10(null); + dto.setTubeMitogene(null); + dto.setTubeMitogeneGT10(null); + } + + @Override + protected void unbindLegacyFields() { + if (drugSusceptibilityField != null) { + fieldGroup.unbind(drugSusceptibilityField); + drugSusceptibilityField = null; + } + } + + private void bindTubePair( + TextField tubeField, + ValueProvider tubeGetter, + Setter tubeSetter, + RadioButtonGroup gt10Field, + ValueProvider gt10Getter, + Setter gt10Setter) { + + binder.forField(tubeField).withConverter(new StringToFloatNullableConverter(tubeField.getCaption())).bind(tubeGetter, tubeSetter); + binder.forField(gt10Field).bind(gt10Getter, gt10Setter); + + tubeField + .addValueChangeListener(e -> StringToFloatNullableConverter.parseLocaleAware(e.getValue()).ifPresent(f -> gt10Field.setValue(f > 10))); + + gt10Field.addValueChangeListener(e -> { + if (Boolean.TRUE.equals(e.getValue())) { + StringToFloatNullableConverter.parseLocaleAware(tubeField.getValue()).filter(f -> f <= 10).ifPresent(f -> tubeField.clear()); + } + }); + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/events/DiseaseChangedEvent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/events/DiseaseChangedEvent.java new file mode 100644 index 00000000000..a9ffdc711c1 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/events/DiseaseChangedEvent.java @@ -0,0 +1,16 @@ +package de.symeda.sormas.ui.samples.events; + +import de.symeda.sormas.api.Disease; + +public class DiseaseChangedEvent { + + private final Disease disease; + + public DiseaseChangedEvent(Disease disease) { + this.disease = disease; + } + + public Disease getDisease() { + return disease; + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/events/SetTestResultEvent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/events/SetTestResultEvent.java new file mode 100644 index 00000000000..a667249b7c9 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/events/SetTestResultEvent.java @@ -0,0 +1,21 @@ +package de.symeda.sormas.ui.samples.events; + +import de.symeda.sormas.api.sample.PathogenTestResultType; + +/** + * Command event: requests that the test result field be set to a specific value. + * Distinct from {@link TestResultChangedEvent}, which notifies that the value has already changed. + * A {@code null} test result means "clear the field". + */ +public class SetTestResultEvent { + + private final PathogenTestResultType testResult; + + public SetTestResultEvent(PathogenTestResultType testResult) { + this.testResult = testResult; + } + + public PathogenTestResultType getTestResult() { + return testResult; + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/events/TestResultChangedEvent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/events/TestResultChangedEvent.java new file mode 100644 index 00000000000..7d35dfd0007 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/events/TestResultChangedEvent.java @@ -0,0 +1,16 @@ +package de.symeda.sormas.ui.samples.events; + +import de.symeda.sormas.api.sample.PathogenTestResultType; + +public class TestResultChangedEvent { + + private final PathogenTestResultType testResult; + + public TestResultChangedEvent(PathogenTestResultType testResult) { + this.testResult = testResult; + } + + public PathogenTestResultType getTestResult() { + return testResult; + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/events/TestTypeChangedEvent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/events/TestTypeChangedEvent.java new file mode 100644 index 00000000000..716ab99d247 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/events/TestTypeChangedEvent.java @@ -0,0 +1,16 @@ +package de.symeda.sormas.ui.samples.events; + +import de.symeda.sormas.api.sample.PathogenTestType; + +public class TestTypeChangedEvent { + + private final PathogenTestType testType; + + public TestTypeChangedEvent(PathogenTestType testType) { + this.testType = testType; + } + + public PathogenTestType getTestType() { + return testType; + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/events/ViaLimsChangedEvent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/events/ViaLimsChangedEvent.java new file mode 100644 index 00000000000..d379ce975d7 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/events/ViaLimsChangedEvent.java @@ -0,0 +1,14 @@ +package de.symeda.sormas.ui.samples.events; + +public class ViaLimsChangedEvent { + + private final boolean viaLims; + + public ViaLimsChangedEvent(boolean viaLims) { + this.viaLims = viaLims; + } + + public boolean isViaLims() { + return viaLims; + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/therapy/DrugSusceptibilityForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/therapy/DrugSusceptibilityForm.java index fef8d0ee77b..2c003c559f9 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/therapy/DrugSusceptibilityForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/therapy/DrugSusceptibilityForm.java @@ -185,8 +185,9 @@ private ComboBox addResistanceResultField(String fieldId) { return field; } + @Override public void markAsDirty() { - + super.markAsDirty(); } public void forceUpdateDrugSusceptibilityFields() { @@ -297,38 +298,43 @@ private void forceUpdateDrugSusceptibilityMicField(String fieldId, Optional