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-backend/src/main/resources/sql/sormas_schema.sql b/sormas-backend/src/main/resources/sql/sormas_schema.sql index 50bbe00d89d..312be0de5ef 100644 --- a/sormas-backend/src/main/resources/sql/sormas_schema.sql +++ b/sormas-backend/src/main/resources/sql/sormas_schema.sql @@ -15770,4 +15770,4 @@ UPDATE diseaseconfiguration SET exposurecategories = 'VECTOR_BORNE' WHERE diseas INSERT INTO schema_version (version_number, comment) VALUES (622, '#13887 default disease exposure category configuration'); --- *** Insert new sql commands BEFORE this line. Remember to always consider _history tables. *** \ No newline at end of file +-- *** Insert new sql commands BEFORE this line. Remember to always consider _history tables. *** 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..9a5bf067a42 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 @@ -42,7 +42,6 @@ import de.symeda.sormas.api.caze.CaseClassification; import de.symeda.sormas.api.caze.CaseDataDto; import de.symeda.sormas.api.caze.CaseReferenceDto; -import de.symeda.sormas.api.common.DeletionReason; import de.symeda.sormas.api.contact.ContactDto; import de.symeda.sormas.api.contact.ContactReferenceDto; import de.symeda.sormas.api.contact.ContactStatus; @@ -137,10 +136,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()); @@ -254,10 +253,7 @@ public CommitDiscardWrapperComponent getPathogenTestEditCompon }); if (pathogenTest.isDeleted()) { - editView.getWrappedComponent().getField(PathogenTestDto.DELETION_REASON).setVisible(true); - if (editView.getWrappedComponent().getField(PathogenTestDto.DELETION_REASON).getValue() == DeletionReason.OTHER_REASON) { - editView.getWrappedComponent().getField(PathogenTestDto.OTHER_DELETION_REASON).setVisible(true); - } + editView.getWrappedComponent().showDeletionInfo(pathogenTest.getDeletionReason()); } editView.restrictEditableComponentsOnEditView( forHumanSample ? UserRight.SAMPLE_EDIT : UserRight.ENVIRONMENT_SAMPLE_EDIT, @@ -354,14 +350,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 +371,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 +415,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 +437,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 eb028305102..faea093a0da 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,258 +18,94 @@ 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.HashSet; import java.util.List; -import java.util.Map; -import java.util.function.BiConsumer; -import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; -import org.apache.commons.collections4.CollectionUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.vaadin.server.UserError; -import com.vaadin.ui.AbstractComponent; +import com.vaadin.shared.Registration; +import com.vaadin.ui.Component; import com.vaadin.ui.Label; -import com.vaadin.v7.data.Property; -import com.vaadin.v7.data.fieldgroup.BeanFieldGroup; -import com.vaadin.v7.data.util.BeanItem; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.v7.data.Validator.InvalidValueException; +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.data.util.converter.Converter.ConversionException; -import com.vaadin.v7.data.util.converter.ConverterUtil; -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; - -import de.symeda.sormas.api.CountryHelper; + 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.common.DeletionReason; 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.Strings; -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.GenoType; -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.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.Serotype; -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.samples.components.AdditionalTestInfoComponent; +import de.symeda.sormas.ui.samples.components.DeletionComponent; +import de.symeda.sormas.ui.samples.components.DiseaseSelectionComponent; +import de.symeda.sormas.ui.samples.components.DiseaseVariantComponent; +import de.symeda.sormas.ui.samples.components.FourFoldCtCqComponent; +import de.symeda.sormas.ui.samples.components.PrescriberComponent; +import de.symeda.sormas.ui.samples.components.ResultTextComponent; +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 final Logger logger = LoggerFactory.getLogger(getClass()); - - private static final String PATHOGEN_TEST_HEADING_LOC = "pathogenTestHeadingLoc"; - - private static final String PRESCRIBER_HEADING_LOC = "prescriberHeading"; - - //@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.PCR_TEST_SPECIFICATION, "") + - fluidRowLocs(PathogenTestDto.TESTED_PATHOGEN, PathogenTestDto.TESTED_PATHOGEN_DETAILS) + - fluidRowLocs(PathogenTestDto.TYPING_ID, "") + - fluidRowLocs(PathogenTestDto.TEST_DATE_TIME, PathogenTestDto.LAB) + - fluidRowLocs(6, "",6, PathogenTestDto.LAB_DETAILS) + - fluidRowLocs(6,PathogenTestDto.TEST_RESULT, 4, PathogenTestDto.TEST_RESULT_VERIFIED, 2,PathogenTestDto.PRELIMINARY) + - fluidRowLocs(6, PathogenTestDto.RESULT_DETAILS,3,PathogenTestDto.PERFORMED_BY_REFERENCE_LABORATORY,3, PathogenTestDto.RETEST_REQUESTED) + - 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, PathogenTestDto.SPECIE_TEXT) + - fluidRowLocs(PathogenTestDto.PATTERN_PROFILE, "") + - fluidRowLocs(PathogenTestDto.DRUG_SUSCEPTIBILITY) + - fluidRowLocs(6,PathogenTestDto.SEROTYPE, 6,PathogenTestDto.SEROTYPE_TEXT) + - fluidRowLocs(6,PathogenTestDto.SEROTYPING_METHOD, 6,PathogenTestDto.SERO_TYPING_METHOD_TEXT) + - fluidRowLocs(6,PathogenTestDto.SERO_GROUP_SPECIFICATION , 6, PathogenTestDto.SERO_GROUP_SPECIFICATION_TEXT) + - fluidRowLocs(6,PathogenTestDto.GENOTYPE,6, PathogenTestDto.GENOTYPE_TEXT) + - fluidRowLocs(6,PathogenTestDto.ANTIBODY_TITRE) + - 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.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, "") + - 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 - - //@formatter:off - // 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))); - put(Disease.DENGUE, new ArrayList<>(List.of(PathogenTestType.NAAT, PathogenTestType.NEUTRALIZING_ANTIBODIES, PathogenTestType.PCR_RT_PCR))); - put(Disease.MALARIA, new ArrayList<>(List.of( PathogenTestType.ANTIGEN_DETECTION, PathogenTestType.THIN_BLOOD_SMEAR, PathogenTestType.RAPID_TEST, - PathogenTestType.INDIRECT_FLUORESCENT_ANTIBODY,PathogenTestType.PCR_RT_PCR, PathogenTestType.Q_PCR, PathogenTestType.ENZYME_LINKED_IMMUNOSORBENT_ASSAY, PathogenTestType.LAMP, - PathogenTestType.OTHER_ANTIGEN_DETECTION_TEST, PathogenTestType.OTHER_SEROLOGICAL_TEST, PathogenTestType.OTHER_MOLECULAR_ASSAY))); - } - }); - - // map to decide the serotype field value and enable/disable state - // Serotype should display, with @Herold code refactor, it should be removed from here. - public static final Map> SEROTYPE_VISIBILITY_MAP = Collections.unmodifiableMap(new HashMap<>() { - { - put(Disease.INVASIVE_PNEUMOCOCCAL_INFECTION, Collections.unmodifiableList(Arrays.asList(PathogenTestType.WHOLE_GENOME_SEQUENCING, - PathogenTestType.SLIDE_AGGLUTINATION, PathogenTestType.MULTILOCUS_SEQUENCE_TYPING, PathogenTestType.SEROGROUPING))); - put(Disease.DENGUE, Collections.unmodifiableList(Arrays.asList(PathogenTestType.NAAT, PathogenTestType.PCR_RT_PCR, PathogenTestType.NEUTRALIZING_ANTIBODIES))); - } - }); - //@formatter:off - 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))); - } - }); - //@formatter:off - // this map is to decide the species field value and enable/disable state. - // this suppose to refactored with @Harold changes - public static final Map> SPECIE_VISIBILITY_MAP = Collections.unmodifiableMap(new HashMap<>() { - - { - put(Disease.LATENT_TUBERCULOSIS, Collections.unmodifiableList(Arrays.asList(PathogenTestType.SPOLIGOTYPING))); - put(Disease.TUBERCULOSIS, Collections.unmodifiableList(Arrays.asList(PathogenTestType.SPOLIGOTYPING))); - put(Disease.MALARIA, Collections.unmodifiableList(Arrays.asList(PathogenTestType.THIN_BLOOD_SMEAR, PathogenTestType.ANTIGEN_DETECTION, - PathogenTestType.RAPID_TEST, PathogenTestType.PCR_RT_PCR, PathogenTestType.Q_PCR, PathogenTestType.LAMP,PathogenTestType.INDIRECT_FLUORESCENT_ANTIBODY, - PathogenTestType.OTHER_MOLECULAR_ASSAY, PathogenTestType.OTHER_SEROLOGICAL_TEST, PathogenTestType.OTHER_ANTIGEN_DETECTION_TEST, - PathogenTestType.ENZYME_LINKED_IMMUNOSORBENT_ASSAY))); - } - }); - //@formatter:on - 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> 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<>() { - - { - put(PathogenTestDto.TESTED_DISEASE, Collections.unmodifiableList(Arrays.asList(Disease.CORONAVIRUS))); - put(PathogenTestDto.TEST_TYPE, Collections.unmodifiableList(Arrays.asList(PathogenTestType.PCR_RT_PCR))); - } - }); + 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 Disease disease; + private PathogenTestFormConfig formConfig; - private Label pathogenTestHeadingLabel; + private final FormEventBus eventBus = new FormEventBus(); + private final List> formComponents = new ArrayList<>(); + private final List eventRegistrations = new ArrayList<>(); - 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 specieField; - private ComboBox genoTypingCB; - private TextField genoTypingResultTextTF; + private Label headingLabel; + private TestIdentificationComponent testIdentificationComponent; + private TestMethodComponent testMethodComponent; + private TestResultComponent testResultComponent; + private DiseaseVariantComponent diseaseVariantComponent; + private DiseaseSelectionComponent diseaseSelectionComponent; + private DeletionComponent deletionComponent; - private ComboBox seroGrpSepcCB; - private TextField seroGrpSpecTxt; - private ComboBox seroTypeField; + private AbstractDiseaseSectionComponent activeSection; + private VerticalLayout diseaseSectionSlot; public PathogenTestForm( AbstractSampleForm sampleForm, @@ -278,6 +114,7 @@ public PathogenTestForm( boolean isPseudonymized, boolean inJurisdiction, Disease disease) { + this(create, caseSampleCount, isPseudonymized, inJurisdiction, disease); this.sampleForm = sampleForm; this.disease = disease; @@ -288,7 +125,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; @@ -299,7 +135,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(); @@ -308,83 +143,168 @@ 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 if (Disease.MALARIA == (Disease) diseaseField.getValue() && testType == PathogenTestType.Q_PCR) { - // CT value should be visible for Malaria, QPCR test. - cqValueField.setVisible(true); + @Override + protected String createHtmlLayout() { + return HTML_LAYOUT; + } + + @Override + protected void addFields() { + formConfig = PathogenTestFormConfig.fromCurrentConfig(); + + VerticalLayout container = new VerticalLayout(); + container.setWidth(100, Unit.PERCENTAGE); + container.setMargin(false); + container.setSpacing(true); + getContent().addComponent(container, COMPONENT_CONTAINER_LOC); + + headingLabel = new Label(); + headingLabel.addStyleName(H3); + container.addComponent(headingLabel); + + testIdentificationComponent = new TestIdentificationComponent(eventBus); + formComponents.add(testIdentificationComponent); + container.addComponent(testIdentificationComponent); + + diseaseSelectionComponent = new DiseaseSelectionComponent(eventBus, disease, create, environmentSample != null); + formComponents.add(diseaseSelectionComponent); + container.addComponent(diseaseSelectionComponent); + + testMethodComponent = new TestMethodComponent(eventBus, this::getSampleDate, disease); + formComponents.add(testMethodComponent); + container.addComponent(testMethodComponent); + + testResultComponent = new TestResultComponent(eventBus, formConfig.isLuxembourg, disease); + formComponents.add(testResultComponent); + container.addComponent(testResultComponent); + + diseaseVariantComponent = new DiseaseVariantComponent(eventBus, disease); + formComponents.add(diseaseVariantComponent); + container.addComponent(diseaseVariantComponent); + + FourFoldCtCqComponent fourFoldCtCqComponent = new FourFoldCtCqComponent(eventBus, caseSampleCount, formConfig.isLuxembourg, disease); + formComponents.add(fourFoldCtCqComponent); + container.addComponent(fourFoldCtCqComponent); + + diseaseSectionSlot = new VerticalLayout(); + diseaseSectionSlot.setWidth(100, Unit.PERCENTAGE); + diseaseSectionSlot.setMargin(false); + diseaseSectionSlot.setSpacing(false); + container.addComponent(diseaseSectionSlot); + + activeSection = DiseaseSectionFactory.forDisease(disease); + activeSection.initialize(getFieldGroup(), eventBus, formConfig, disease); + activeSection.setVisibilityCallback(visible -> diseaseSectionSlot.setVisible(visible)); + diseaseSectionSlot.addComponent(activeSection); + + AdditionalTestInfoComponent additionalTestInfoComponent = new AdditionalTestInfoComponent(); + formComponents.add(additionalTestInfoComponent); + container.addComponent(additionalTestInfoComponent); + + ResultTextComponent resultTextComponent = new ResultTextComponent(); + formComponents.add(resultTextComponent); + container.addComponent(resultTextComponent); + + PrescriberComponent prescriberComponent = new PrescriberComponent(); + formComponents.add(prescriberComponent); + container.addComponent(prescriberComponent); + + deletionComponent = new DeletionComponent(); + formComponents.add(deletionComponent); + container.addComponent(deletionComponent); + + wireEvents(); + + finalizeForm(); + } + + 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 + eventRegistrations.add(diseaseVariantComponent.getDiseaseVariantField().addValueChangeListener(e -> { + DiseaseVariant variant = e.getValue(); + if (variant != null) { + testResultComponent.getTestResultField().setValue(PathogenTestResultType.POSITIVE); } else { - cqValueField.setVisible(false); - cqValueField.clear(); + testResultComponent.getTestResultField().clear(); } - } + })); } - private void updateDrugSusceptibilityFieldSpecifications(PathogenTestType testType, Disease disease) { + private void finalizeForm() { + testMethodComponent.setLabRequired(SamplePurpose.INTERNAL.equals(getSamplePurpose())); + + initializeAccessAndAllowedAccesses(); + initializeVisibilitiesAndAllowedVisibilities(); - // 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); + for (FormComponent comp : formComponents) { + if (fieldVisibilityCheckers != null) { + comp.applyVisibility(fieldVisibilityCheckers, PathogenTestDto.class); + } + if (fieldAccessCheckers != null) { + comp.applyAccess(fieldAccessCheckers, PathogenTestDto.class); + } } - // 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 (fieldVisibilityCheckers != null) { + activeSection.applyVisibility(fieldVisibilityCheckers, PathogenTestDto.class); } - // if the test type is null we just clear the result field - if (testType == null) { - testResultField.setValue(null); - return; + if (fieldAccessCheckers != null) { + activeSection.applyAccess(fieldAccessCheckers, PathogenTestDto.class); } - // FIXME: why was this here originally? - // TODO: move this to another place, should be in listeners for disease/testType. + } + + private void swapDiseaseSection(Disease newDisease) { + AbstractDiseaseSectionComponent newSection = DiseaseSectionFactory.forDisease(newDisease); + if (newSection.getClass() == activeSection.getClass()) { + return; + } - if ((FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG))) { + activeSection.cleanup(); + diseaseSectionSlot.removeAllComponents(); - // 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); - } + activeSection = newSection; + activeSection.initialize(getFieldGroup(), eventBus, formConfig, newDisease); + activeSection.setVisibilityCallback(visible -> diseaseSectionSlot.setVisible(visible)); + diseaseSectionSlot.addComponent(activeSection); - // testResult=POSITIVE for Tuberculosis diseases, test type SPOLIGOTYPING - if ((disease == Disease.LATENT_TUBERCULOSIS || disease == Disease.TUBERCULOSIS) && (testType == PathogenTestType.SPOLIGOTYPING)) { - testResultField.setValue(PathogenTestResultType.POSITIVE); - } + PathogenTestDto dto = getValue(); + if (dto != null) { + activeSection.setDto(dto); + } - // 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); - } + if (fieldVisibilityCheckers != null) { + activeSection.applyVisibility(fieldVisibilityCheckers, PathogenTestDto.class); } + if (fieldAccessCheckers != null) { + activeSection.applyAccess(fieldAccessCheckers, PathogenTestDto.class); + } } private Date getSampleDate() { @@ -411,848 +331,115 @@ private SamplePurpose getSamplePurpose() { } @Override - protected String createHtmlLayout() { - return HTML_LAYOUT; - } - - @Override - public void setHeading(String heading) { - pathogenTestHeadingLabel.setValue(heading); - } - - @Override - 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()); - } - typingIdField.setValue(newFieldValue.getTypingId()); - specieField.setValue(newFieldValue.getSpecie()); - if (!genoTypingCB.isReadOnly()) { - genoTypingCB.setValue(newFieldValue.getGenoType()); + public void preCommit(CommitEvent commitEvent) throws CommitException { + super.preCommit(commitEvent); - } + List allCauses = new ArrayList<>(); - if (!genoTypingResultTextTF.isReadOnly()) { - genoTypingResultTextTF.setValue(newFieldValue.getGenoTypeText()); + for (FormComponent comp : formComponents) { + try { + comp.validate(); + } catch (InvalidValueException e) { + collectCauses(e, allCauses); + } } - if (!seroGrpSepcCB.isReadOnly()) { - seroGrpSepcCB.setValue(newFieldValue.getSeroGroupSpecification()); + if (activeSection != null) { + try { + activeSection.validate(); + } catch (InvalidValueException e) { + collectCauses(e, allCauses); + } } - if (!seroGrpSpecTxt.isReadOnly()) { - seroGrpSpecTxt.setValue(newFieldValue.getSeroGroupSpecificationText()); + if (!allCauses.isEmpty()) { + String joinedCaptions = allCauses.stream().map(InvalidValueException::getMessage).collect(Collectors.joining(", ")); + throw new InvalidValueException(joinedCaptions, allCauses.toArray(new InvalidValueException[0])); } - - drugSusceptibilityField.forceUpdateDrugSusceptibilityFields(); - markAsDirty(); } - @Override - protected void addFields() { - - pathogenTestHeadingLabel = new Label(); - pathogenTestHeadingLabel.addStyleName(H3); - getContent().addComponent(pathogenTestHeadingLabel, PATHOGEN_TEST_HEADING_LOC); - - addDateField(PathogenTestDto.REPORT_DATE, DateField.class, 0); - CheckBox 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); - 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); - 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; - } - - 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())))); - - }); - ComboBox lab = addInfrastructureField(PathogenTestDto.LAB); - lab.addItems(FacadeProvider.getFacilityFacade().getAllActiveLaboratories(true)); - TextField 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 - diseaseField = addDiseaseField(PathogenTestDto.TESTED_DISEASE, true, create, false); - addField(PathogenTestDto.TESTED_DISEASE_DETAILS, TextField.class); - ComboBox diseaseVariantField = addCustomizableEnumField(PathogenTestDto.TESTED_DISEASE_VARIANT); - diseaseVariantField.setNullSelectionAllowed(true); - diseaseVariantField.setVisible(false); - TextField 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)); - } - genoTypingCB = addField(PathogenTestDto.GENOTYPE, ComboBox.class); - genoTypingCB.setVisible(true); - genoTypingResultTextTF = addField(PathogenTestDto.GENOTYPE_TEXT, TextField.class); - genoTypingResultTextTF.setVisible(true); - - 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); - } else { - testedPathogenDetailsField.clear(); - testedPathogenDetailsField.setVisible(false); - } - }); - - if (environmentSample == null) { - diseaseField.setVisible(true); - diseaseField.setRequired(true); - - testedPathogenField.setVisible(false); - testedPathogenField.setRequired(false); + private static void collectCauses(InvalidValueException e, List target) { + if (e.getCauses().length > 0) { + Collections.addAll(target, e.getCauses()); } else { - diseaseField.setVisible(false); - diseaseField.setRequired(false); - - testedPathogenField.setVisible(true); - testedPathogenField.setRequired(true); - } - - testResultField = addField(PathogenTestDto.TEST_RESULT, ComboBox.class); - testResultField.removeItem(PathogenTestResultType.NOT_DONE); - - if (!FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { - testResultField.removeItem(PathogenTestResultType.NOT_APPLICABLE); + target.add(e); } - seroTypeField = addField(PathogenTestDto.SEROTYPE, ComboBox.class); - addField(PathogenTestDto.SEROTYPE_TEXT, 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); - - addField(PathogenTestDto.SPECIE_TEXT, TextField.class); - - 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); - - // Malaria and Dengue fields - addField(PathogenTestDto.ANTIBODY_TITRE, TextField.class); - addField(PathogenTestDto.PERFORMED_BY_REFERENCE_LABORATORY, NullableOptionGroup.class); - addField(PathogenTestDto.RETEST_REQUESTED, NullableOptionGroup.class); - Field resultDetailsField = addField(PathogenTestDto.RESULT_DETAILS); - resultDetailsField.setVisible(false); - - 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); + @Override + public void setHeading(String heading) { + headingLabel.setValue(heading); + } - //tuberculosis-miru-code test specification - Map> tuberculosisMiruCodeDependencies = new HashMap<>() { + @Override + public void setValue(PathogenTestDto newFieldValue) throws ReadOnlyException, Converter.ConversionException { + super.setValue(newFieldValue); - { - 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); + for (FormComponent comp : formComponents) { + comp.setDto(newFieldValue); } - seroTypeField.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); + if (activeSection != null) { + activeSection.setDto(newFieldValue); } - 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, - 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 - addFields( - FieldConfiguration.builder(PathogenTestDto.TUBE_NIL) - .validationMessageProperty(Validations.onlyNumbersAllowed) - .valueChangeListener(new TuberculosisIGRAInputValueChangeListener(getFieldGroup(), PathogenTestDto.TUBE_NIL,PathogenTestDto.TUBE_NIL_GT10)) - .build(), - FieldConfiguration.builder(PathogenTestDto.TUBE_AG_TB1) - .validationMessageProperty(Validations.onlyNumbersAllowed) - .valueChangeListener(new TuberculosisIGRAInputValueChangeListener(getFieldGroup(), PathogenTestDto.TUBE_AG_TB1,PathogenTestDto.TUBE_AG_TB1_GT10)).build(), - FieldConfiguration.builder(PathogenTestDto.TUBE_AG_TB2) - .validationMessageProperty(Validations.onlyNumbersAllowed) - .valueChangeListener(new TuberculosisIGRAInputValueChangeListener(getFieldGroup(), PathogenTestDto.TUBE_AG_TB2,PathogenTestDto.TUBE_AG_TB2_GT10)).build(), - FieldConfiguration.builder(PathogenTestDto.TUBE_MITOGENE) - .validationMessageProperty(Validations.onlyNumbersAllowed) - .valueChangeListener(new TuberculosisIGRAInputValueChangeListener(getFieldGroup(), PathogenTestDto.TUBE_MITOGENE,PathogenTestDto.TUBE_MITOGENE_GT10)).build()); - //@formatter:on - - //@formatter:off - addFields( - FieldConfiguration.builder(PathogenTestDto.TUBE_NIL_GT10).valueChangeListener(new TuberculosisIGRAGT10InputValueChangeListener(getFieldGroup(), PathogenTestDto.TUBE_NIL_GT10,PathogenTestDto.TUBE_NIL)).build(), - FieldConfiguration.builder(PathogenTestDto.TUBE_AG_TB1_GT10).valueChangeListener(new TuberculosisIGRAGT10InputValueChangeListener(getFieldGroup(), PathogenTestDto.TUBE_AG_TB1_GT10,PathogenTestDto.TUBE_AG_TB1)).build(), - FieldConfiguration.builder(PathogenTestDto.TUBE_AG_TB2_GT10).valueChangeListener(new TuberculosisIGRAGT10InputValueChangeListener(getFieldGroup(), PathogenTestDto.TUBE_AG_TB2_GT10,PathogenTestDto.TUBE_AG_TB2)).build(), - FieldConfiguration.builder(PathogenTestDto.TUBE_MITOGENE_GT10).valueChangeListener(new TuberculosisIGRAGT10InputValueChangeListener(getFieldGroup(), PathogenTestDto.TUBE_MITOGENE_GT10,PathogenTestDto.TUBE_MITOGENE)).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); - - // 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())); - - CheckBox fourFoldIncrease = addField(PathogenTestDto.FOUR_FOLD_INCREASE_ANTIBODY_TITER, CheckBox.class); - CssStyles.style(fourFoldIncrease, VSPACE_3, VSPACE_TOP_4); - fourFoldIncrease.setVisible(false); - fourFoldIncrease.setEnabled(false); - - addField(PathogenTestDto.TEST_RESULT_TEXT, TextArea.class).setRows(6); - - 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()))); - - addFields(PathogenTestDto.PRESCRIBER_ADDRESS, PathogenTestDto.PRESCRIBER_POSTAL_CODE, PathogenTestDto.PRESCRIBER_CITY); - ComboBox prescriberCountrField = addInfrastructureField(PathogenTestDto.PRESCRIBER_COUNTRY); - FieldHelper.updateItems(prescriberCountrField, FacadeProvider.getCountryFacade().getAllActiveAsReference()); - - addField(PathogenTestDto.DELETION_REASON); - 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, - 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); - - 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); - - // antibody titre visibility - FieldHelper.setVisibleWhen( - getFieldGroup(), - PathogenTestDto.ANTIBODY_TITRE, - PathogenTestDto.TEST_TYPE, - PathogenTestType.NEUTRALIZING_ANTIBODIES, - 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, cryptoGenoTypingDependencies, true); - - FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.GENOTYPE_TEXT, PathogenTestDto.GENOTYPE, GenoType.OTHER, 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); - - Consumer updateDiseaseVariantField = disease -> { - List diseaseVariants = - FacadeProvider.getCustomizableEnumFacade().getEnumValues(CustomizableEnumType.DISEASE_VARIANT, disease); - FieldHelper.updateItems(diseaseVariantField, diseaseVariants); - diseaseVariantField.setVisible( - disease != null && isVisibleAllowed(PathogenTestDto.TESTED_DISEASE_VARIANT) && CollectionUtils.isNotEmpty(diseaseVariants)); - }; - - updateDiseaseVariantField.accept((Disease) diseaseField.getValue()); - - // Need to address these visibility issues - // @Herold - BiConsumer updateSerotypeField = (Disease disease, PathogenTestType testType) -> { - setVisibleClear( - SEROTYPE_VISIBILITY_MAP.containsKey(disease) && SEROTYPE_VISIBILITY_MAP.get(disease).contains(testType), - PathogenTestDto.SEROTYPE); - }; - - BiConsumer updateSpecieField = (Disease disease, PathogenTestType testType) -> { - setVisibleClear( - SPECIE_VISIBILITY_MAP.containsKey(disease) && SPECIE_VISIBILITY_MAP.get(disease).contains(testType), - PathogenTestDto.SPECIE); - }; - - diseaseField.addValueChangeListener((ValueChangeListener) 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); - - FieldHelper.updateItems( - testTypeField, - Arrays.asList(PathogenTestType.values()), - FieldVisibilityCheckers.withDisease(disease), - PathogenTestType.class); - // serotype values should be changed based on the disease - // FieldHelper.updateItems(seroTypeField, Arrays.asList(Serotype.values()), FieldVisibilityCheckers.withDisease(disease), Serotype.class); - FieldHelper.updateItems(disease, seroTypeField, Serotype.class); - FieldHelper.updateItems(disease, specieField, PathogenSpecie.class); - if (FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { - FieldHelper.updateItems( - strainCallStatusField, - Arrays.asList(PathogenStrainCallStatus.values()), - FieldVisibilityCheckers.withDisease(disease), - PathogenStrainCallStatus.class); - - updateDrugSusceptibilityFieldSpecifications((PathogenTestType) testTypeField.getValue(), disease); - } - }); - 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) { - // For Dengue IGG serum antibody, fourFoldIncrease fild should be visible. - // and its caption will be renamed with the caption as Seroconversion/ 4-fold increase - if (Disease.DENGUE == (Disease) diseaseField.getValue() && testType == PathogenTestType.IGG_SERUM_ANTIBODY) { - fourFoldIncrease.setCaption(I18nProperties.getCaption(Captions.PathogenTest_fourFoldIncreaseAntibodyTiter_DENGUE)); - fourFoldIncrease.setVisible(true); - fourFoldIncrease.setEnabled(true); - } else 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, - 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); - } 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); - } - // 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, GenoType.class); - // verifying the serotype field visibility. reason for this pattern is that, this should display disease+pathogentest combination. - updateSerotypeField.accept(disease, testType); - - updateSpecieField.accept(disease, testType); - // Result details should be visible for Malaria and test-types with PathogenTestType.THIN_BLOOD_SMEAR, PathogenTestType.Q_PCR - setVisibleClear( - Disease.MALARIA == disease && Arrays.asList(PathogenTestType.THIN_BLOOD_SMEAR, PathogenTestType.Q_PCR).contains(testType), - PathogenTestDto.RESULT_DETAILS); - setVisibleClear( - testType == PathogenTestType.SEROGROUPING && Disease.INVASIVE_PNEUMOCOCCAL_INFECTION == disease, - PathogenTestDto.SEROTYPING_METHOD); - } 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); - testResultField.clear(); - testResultField.setEnabled(true); - } - - if (RESULT_FIELD_DECISION_MAP.containsKey(disease) && RESULT_FIELD_DECISION_MAP.get(disease).contains(testType)) { - testResultField.setValue(PathogenTestResultType.POSITIVE); - } else { - testResultField.clear(); - } - - updateDrugSusceptibilityFieldSpecifications(testType, (Disease) diseaseField.getValue()); - }); - - 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); - }); - - if (SamplePurpose.INTERNAL.equals(getSamplePurpose())) { // this only works for already saved samples - setRequired(true, PathogenTestDto.LAB); - } - setRequired(true, PathogenTestDto.TEST_TYPE, PathogenTestDto.TEST_RESULT); + markAsDirty(); + } - initializeAccessAndAllowedAccesses(); - initializeVisibilitiesAndAllowedVisibilities(); + /** Reveals the typingId field if the existing test has a preset value. */ + public void showTypingIdIfPreset(String typingId) { + testMethodComponent.showTypingIdIfPreset(typingId); + } - // displaying the serotype text field only if the serotype is "other" and it has the visibility - if (isVisibleAllowed(PathogenTestDto.SEROTYPE)) { - // FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.SEROTYPE, SEROTYPE_VISIBILITY_MAP, true); - FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.SEROTYPE_TEXT, PathogenTestDto.SEROTYPE, Serotype.OTHER, true); - } - if (isVisibleAllowed(PathogenTestDto.SPECIE)) { - FieldHelper.setVisibleWhen(getFieldGroup(), PathogenTestDto.SPECIE_TEXT, PathogenTestDto.SPECIE, PathogenSpecie.OTHER, true); + /** Sets initial field values for a newly created pathogen test. */ + public void initializeForNewTest(Disease disease) { + testResultComponent.getTestResultField().setValue(PathogenTestResultType.PENDING); + if (disease != null) { + diseaseSelectionComponent.getDiseaseField().setValue(disease); } + } - // 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)); + /** Updates lab field required state (e.g. when sample purpose changes). */ + public void setLabRequired(boolean required) { + testMethodComponent.setLabRequired(required); } /** - * This class is to be used for the Tuberculosis IGRA input value change listeners. - * It will check/uncheck the Tuberculosis IGRA greater than 10 checkbox dependiong on the value of the input field. - *

    - * Note: ideally a custom component should be used for both fields, to avoid potential race conditions between the two listeners. + * Registers a validator ensuring test date is not before sample date. + * Evaluated during form commit via {@link de.symeda.sormas.ui.utils.FormComponent#validate()}. */ - protected static class TuberculosisIGRAInputValueChangeListener implements ValueChangeListener { - - private final String igraInputFieldId; - private final String igraGT10FieldId; - private final BeanFieldGroup fieldGroup; - - public TuberculosisIGRAInputValueChangeListener(BeanFieldGroup fg, String igraInputFieldId, String igraGT10FieldId) { - this.igraInputFieldId = igraInputFieldId; - this.igraGT10FieldId = igraGT10FieldId; - this.fieldGroup = fg; - } - - @SuppressWarnings({ - "unchecked", - "rawtypes" }) - @Override - public void valueChange(Property.ValueChangeEvent event) { - - final Field igraInputField = fieldGroup.getField(igraInputFieldId); - - if (igraInputField == null) { - return; - } - - if (igraInputField instanceof AbstractComponent) { - ((AbstractComponent) igraInputField).setComponentError(null); - } - - final BeanItem beanItemDataSource = fieldGroup.getItemDataSource(); - - // the input field is always a TextField with a String as value - // we need to make a hard assumtion that the input field value is a Float - - // we check to see if the model property is numeric - // the model at this point will not be updated, so we only check type - final Property igraValueProp = beanItemDataSource.getItemProperty(igraInputFieldId); - - if (!Number.class.isAssignableFrom(igraValueProp.getType())) { - // we will not deal with non-numeric values - return; - } - - // we know that the model property is numeric - // we could get the original value with: igraValueProp.getValue(); - - // we need to convert the value to number - // and we need to do it locale aware and need to finagle with types - - Number igraNewValue = null; - - try { - igraNewValue = igraInputField.getValue() == null - ? null - : (Number) ConverterUtil - .getConverter(igraInputField.getType(), (Class) igraValueProp.getType(), null /* current session */) - .convertToModel(igraInputField.getValue(), igraValueProp.getType(), igraInputField.getLocale()); - } catch (ConversionException e) { - if (igraInputField instanceof AbstractComponent) { - ((AbstractComponent) igraInputField).setComponentError(new UserError(I18nProperties.getString(Strings.errorInvalidValue))); - } - return; - } - - final Boolean checked = igraNewValue == null ? null : igraNewValue.floatValue() > 10; - - // now we need to set the value of the GT10 field - @SuppressWarnings("unchecked") - final Field igraGT10Field = (Field) fieldGroup.getField(igraGT10FieldId); - if (igraGT10Field == null) { - // if we can't find the field, we don't care - return; - } - - // lets make sure the property is a boolean - final Property igraGT10Prop = beanItemDataSource.getItemProperty(igraGT10FieldId); - if (igraGT10Prop == null || !Boolean.class.isAssignableFrom(igraGT10Prop.getType())) { - // if we can't find the property, or we can't set it we don't care - return; - } - - // now field is supposed to be a boolean - // booleans come in two flavors: collection based and primitive - final boolean isCollection = Collection.class.isAssignableFrom(igraGT10Field.getType()); - - if (!isCollection) { - // primitive booleans are easy - final boolean currentChecked = Boolean.TRUE.equals(igraGT10Field.getValue()); - if (checked != null && checked.booleanValue() != currentChecked) { - igraGT10Field.setValue(checked); - } - } else { - // well have to do it the hard way - final Collection currentSet = (Collection) igraGT10Field.getValue(); - final boolean currentChecked = currentSet != null && !currentSet.isEmpty() && currentSet.contains(Boolean.TRUE); - if (checked != null && checked.booleanValue() != currentChecked) { - final HashSet set = new HashSet<>(); - set.add(checked); - igraGT10Field.setValue(Collections.unmodifiableSet(set)); - } - } - } + public void addTestDateAfterSampleDateValidator(Supplier sampleDateSupplier, String errorMessage) { + testMethodComponent.addTestDateAfterSampleDateValidator(sampleDateSupplier, errorMessage); } - /** - * This class is to be used for the Tuberculosis IGRA greater than 10 checkboxes value change listeners. - * It will clear the associated input field if the checkbox is checked and the - * value is less than or equal to 10. - * In reverse if the value is greater than 10 and the checkbox is not checked it will clear the input field. - *

    - * Note: ideally a custom component should be used for both fields, to avoid potential race conditions between the two listeners. - */ - protected static class TuberculosisIGRAGT10InputValueChangeListener implements ValueChangeListener { + /** Sets the VIA_LIMS checkbox value. */ + public void setViaLims(boolean checked) { + testIdentificationComponent.getViaLimsField().setValue(checked); + } - private final String igraInputFieldId; - private final String igraGT10FieldId; - private final BeanFieldGroup fieldGroup; + /** Shows deletion reason fields for a deleted record. */ + public void showDeletionInfo(DeletionReason reason) { + deletionComponent.showForDeletedRecord(reason); + } - public TuberculosisIGRAGT10InputValueChangeListener(BeanFieldGroup fg, String igraGT10FieldId, String igraInputFieldId) { - this.igraInputFieldId = igraInputFieldId; - this.igraGT10FieldId = igraGT10FieldId; - this.fieldGroup = fg; + @Override + public void detach() { + for (Registration reg : eventRegistrations) { + reg.remove(); } + eventRegistrations.clear(); - @SuppressWarnings({ - "rawtypes", - "unchecked" }) - @Override - public void valueChange(Property.ValueChangeEvent event) { - - // let's try to get the numeric input field and converted value - final Field igraInputField = fieldGroup.getField(igraInputFieldId); - if (igraInputField == null) { - return; - } - - if (igraInputField instanceof AbstractComponent) { - ((AbstractComponent) igraInputField).setComponentError(null); - } - - final BeanItem beanItemDataSource = fieldGroup.getItemDataSource(); - - final Property igraValueProp = beanItemDataSource.getItemProperty(igraInputFieldId); - if (igraValueProp == null || !Number.class.isAssignableFrom(igraValueProp.getType())) { - return; - } - - // lets make sure the GT10 property is a boolean - final Property igraGT10Prop = beanItemDataSource.getItemProperty(igraGT10FieldId); - if (igraGT10Prop == null || !Boolean.class.isAssignableFrom(igraGT10Prop.getType())) { - // if we can't find the property, or we can't set it we don't care - return; - } - - Number igraNewValue = null; - - try { - igraNewValue = igraInputField.getValue() == null - ? null - : (Number) ConverterUtil - .getConverter(igraInputField.getType(), (Class) igraValueProp.getType(), null /* current session */) - .convertToModel(igraInputField.getValue(), igraValueProp.getType(), igraInputField.getLocale() /* current locale */); - } catch (ConversionException e) { - if (igraInputField instanceof AbstractComponent) { - ((AbstractComponent) igraInputField).setComponentError(new UserError(I18nProperties.getString(Strings.errorInvalidValue))); - } - return; - } - - // now let's try to determine if the checkbox is checked (we know it's a boolean) - @SuppressWarnings("unchecked") - final Field igraGT10Field = (Field) fieldGroup.getField(igraGT10FieldId); - if (igraGT10Field == null) { - // if we can't find the field, we don't care - return; - } - - // booleans come in two flavors: collection based and primitive - final boolean isCollection = Collection.class.isAssignableFrom(igraGT10Field.getType()); - - Boolean checked = false; - - // value can be true or false/null(presumed false) - if (!isCollection) { - // primitive booleans are easy - checked = igraGT10Field.getValue() == null ? null : Boolean.TRUE.equals(igraGT10Field.getValue()); - } else { - Collection set = (Collection) igraGT10Field.getValue(); - checked = set == null || set.isEmpty() ? null : set.contains(Boolean.TRUE); - } - - if (checked == null) { // the checbox is neither checked nor unchecked - checked = igraNewValue != null && igraNewValue.floatValue() > 10; - - if (!isCollection) { - // primitive booleans are easy - igraGT10Field.setValue(checked); - } else { - final HashSet set = new HashSet<>(); - set.add(checked); - igraGT10Field.setValue(Collections.unmodifiableSet(set)); - } - - // don't need to clear anything else because there was no check/uncheck before - return; - } + super.detach(); + } - if ((checked && igraNewValue != null && igraNewValue.floatValue() <= 10) // checked but value is filled in and less than 10 - || (!checked && igraNewValue != null && igraNewValue.floatValue() > 10) // not checked but value is filled in an greater than 10 - ) { - try { - igraInputField.clear(); - } catch (ReadOnlyException ex) { - // ignore read-only - } + @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/AdditionalTestInfoComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/AdditionalTestInfoComponent.java new file mode 100644 index 00000000000..e50c9c28a12 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/AdditionalTestInfoComponent.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * 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.components; + +import com.vaadin.ui.RadioButtonGroup; + +import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.ui.utils.FormComponent; + +/** + * Additional test information fields: performed by reference laboratory and retest requested. + * Applicable to all diseases. + */ +public class AdditionalTestInfoComponent extends FormComponent { + + private static final long serialVersionUID = 1L; + + private RadioButtonGroup performedByReferenceLaboratory; + private RadioButtonGroup retestRequested; + + public AdditionalTestInfoComponent() { + super(PathogenTestDto.class); + buildLayout(); + bindFields(); + } + + private void buildLayout() { + performedByReferenceLaboratory = + createBooleanRadioGroup(PathogenTestDto.PERFORMED_BY_REFERENCE_LABORATORY, PathogenTestDto.I18N_PREFIX); + retestRequested = + createBooleanRadioGroup(PathogenTestDto.RETEST_REQUESTED, PathogenTestDto.I18N_PREFIX); + addRow(performedByReferenceLaboratory, retestRequested); + } + + private void bindFields() { + binder.forField(performedByReferenceLaboratory) + .bind(PathogenTestDto::getPerformedByReferenceLaboratory, PathogenTestDto::setPerformedByReferenceLaboratory); + binder.forField(retestRequested) + .bind(PathogenTestDto::getRetestRequested, PathogenTestDto::setRetestRequested); + } +} 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..9dd103ab652 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/CtCqValueComponent.java @@ -0,0 +1,141 @@ +/******************************************************************************* + * 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.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 + || (disease == Disease.MALARIA && testType == PathogenTestType.Q_PCR)) { + 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..22dc8e464e2 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/DeletionComponent.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * 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.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); + } + + /** Shows the deletion reason field (and other reason if applicable) for a deleted record. */ + public void showForDeletedRecord(DeletionReason reason) { + deletionReasonField.setVisible(true); + otherReasonField.setVisible(reason == DeletionReason.OTHER_REASON); + updateRowAndSelfVisibility(); + } +} 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..4b50531295f --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/DiseaseSelectionComponent.java @@ -0,0 +1,159 @@ +/******************************************************************************* + * 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.components; + +import java.util.List; + +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.Disease; +import de.symeda.sormas.api.FacadeProvider; +import de.symeda.sormas.api.customizableenum.CustomizableEnumType; +import de.symeda.sormas.api.environment.environmentsample.Pathogen; +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. + * Vaadin 8 components with own Binder, self-managed visibility. + * Disease variant fields are in {@link DiseaseVariantComponent}. + */ +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; + + 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); + + // Environment vs human sample visibility + if (isEnvironmentSample) { + diseaseField.setVisible(false); + diseaseDetailsField.setVisible(false); + testedPathogenField.setVisible(true); + } else { + diseaseField.setVisible(true); + testedPathogenField.setVisible(false); + } + + // Auto-select default disease on creation when server has a single active disease + if (create && !isEnvironmentSample) { + Disease defaultDisease = FacadeProvider.getDiseaseConfigurationFacade().getDefaultDisease(); + if (defaultDisease != null) { + diseaseField.setValue(defaultDisease); + } + } + } + + 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); + } + + private void wireEvents() { + // Disease change: 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(); + } + + 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(); + } + })); + } + + public ComboBox getDiseaseField() { + return diseaseField; + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/DiseaseVariantComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/DiseaseVariantComponent.java new file mode 100644 index 00000000000..b2bf71fb274 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/DiseaseVariantComponent.java @@ -0,0 +1,164 @@ +/******************************************************************************* + * 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.components; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +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.i18n.Captions; +import de.symeda.sormas.api.i18n.I18nProperties; +import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.api.sample.PathogenTestType; +import de.symeda.sormas.ui.samples.events.DiseaseChangedEvent; +import de.symeda.sormas.ui.samples.events.TestTypeChangedEvent; +import de.symeda.sormas.ui.utils.FormComponent; +import de.symeda.sormas.ui.utils.FormEventBus; + +/** + * Disease variant selection with details support. + * as a standalone composable component + */ +public class DiseaseVariantComponent extends FormComponent { + + private static final long serialVersionUID = 1L; + + private static final Set VARIANT_ALLOWED_TEST_TYPES = new HashSet<>( + Arrays.asList( + PathogenTestType.SEQUENCING, + PathogenTestType.WHOLE_GENOME_SEQUENCING, + PathogenTestType.PCR_RT_PCR, + PathogenTestType.ISOLATION, + PathogenTestType.OTHER)); + + private final FormEventBus eventBus; + private Disease currentDisease; + private PathogenTestType currentTestType; + private List currentVariants; + + private ComboBox diseaseVariantField; + private TextField diseaseVariantDetailsField; + private Label variantDetailsSpacer; + private HorizontalLayout variantRow; + + public DiseaseVariantComponent(FormEventBus eventBus, Disease initialDisease) { + super(PathogenTestDto.class); + this.eventBus = eventBus; + this.currentDisease = initialDisease; + buildLayout(); + bindFields(); + wireEvents(); + } + + private void buildLayout() { + 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); + + updateCaptions(currentDisease); + + variantDetailsSpacer = createSpacer(); + variantRow = addToggleRow(diseaseVariantField, diseaseVariantDetailsField, variantDetailsSpacer); + + refreshVariantItems(); + updateVisibility(); + } + + private void bindFields() { + binder.forField(diseaseVariantField).bind(PathogenTestDto::getTestedDiseaseVariant, PathogenTestDto::setTestedDiseaseVariant); + binder.forField(diseaseVariantDetailsField) + .bind(PathogenTestDto::getTestedDiseaseVariantDetails, PathogenTestDto::setTestedDiseaseVariantDetails); + } + + private void wireEvents() { + // 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); + })); + + // Disease change -> update variant list, captions, and visibility + track(eventBus.on(DiseaseChangedEvent.class, event -> { + currentDisease = event.getDisease(); + refreshVariantItems(); + updateCaptions(currentDisease); + updateVisibility(); + })); + + // Test type change -> update visibility + track(eventBus.on(TestTypeChangedEvent.class, event -> { + currentTestType = event.getTestType(); + updateVisibility(); + })); + } + + private void refreshVariantItems() { + currentVariants = FacadeProvider.getCustomizableEnumFacade().getEnumValues(CustomizableEnumType.DISEASE_VARIANT, currentDisease); + diseaseVariantField.setItems(currentVariants); + } + + private void updateVisibility() { + boolean visible = currentDisease != null + && DiseaseHelper.SUBTYPE_ALLOWED_DISEASES.contains(currentDisease) + && VARIANT_ALLOWED_TEST_TYPES.contains(currentTestType) + && CollectionUtils.isNotEmpty(currentVariants); + diseaseVariantField.setVisible(visible); + if (!visible) { + diseaseVariantField.clear(); + diseaseVariantDetailsField.clear(); + } + updateRowVisibility(variantRow); + setVisible(variantRow.isVisible()); + } + + private void updateCaptions(Disease disease) { + if (DiseaseHelper.SUBTYPE_ALLOWED_DISEASES.contains(disease)) { + diseaseVariantField.setCaption(I18nProperties.getCaption(Captions.PathogenTest_rsv_testedDiseaseVariant)); + diseaseVariantDetailsField.setCaption(I18nProperties.getCaption(Captions.PathogenTest_rsv_testedDiseaseVariantDetails)); + } + } + + public ComboBox getDiseaseVariantField() { + return diseaseVariantField; + } + + public TextField getDiseaseVariantDetailsField() { + return diseaseVariantDetailsField; + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/FourFoldCtCqComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/FourFoldCtCqComponent.java new file mode 100644 index 00000000000..eaeb6a8bb7a --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/FourFoldCtCqComponent.java @@ -0,0 +1,172 @@ +/******************************************************************************* + * 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.components; + +import com.vaadin.ui.CheckBox; + +import de.symeda.sormas.api.Disease; +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.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.TestResultChangedEvent; +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; + +/** + * Four-fold antibody increase checkbox and CT/CQ value fields. + * as a standalone composable component + */ +public class FourFoldCtCqComponent extends FormComponent { + + private static final long serialVersionUID = 1L; + + private final FormEventBus eventBus; + private final int caseSampleCount; + private final CtCqValueComponent ctCqValueComponent; + + private CheckBox fourFoldIncrease; + + private Disease currentDisease; + private PathogenTestType currentTestType; + private PathogenTestResultType currentTestResult; + + public FourFoldCtCqComponent(FormEventBus eventBus, int caseSampleCount, boolean isLuxembourg, Disease initialDisease) { + super(PathogenTestDto.class); + this.eventBus = eventBus; + this.caseSampleCount = caseSampleCount; + this.currentDisease = initialDisease; + this.ctCqValueComponent = new CtCqValueComponent(isLuxembourg); + buildLayout(); + bindFields(); + wireEvents(); + } + + private void buildLayout() { + 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); + + addComponent(ctCqValueComponent); + } + + private void bindFields() { + binder.forField(fourFoldIncrease).bind(PathogenTestDto::isFourFoldIncreaseAntibodyTiter, PathogenTestDto::setFourFoldIncreaseAntibodyTiter); + } + + private void wireEvents() { + // Listen for test type changes + track(eventBus.on(TestTypeChangedEvent.class, event -> { + PathogenTestType testType = event.getTestType(); + currentTestType = testType; + + if (testType != null) { + updateFourFoldIncrease(testType); + + ctCqValueComponent.updateCtVisibility(currentDisease, testType); + } else { + ctCqValueComponent.updateCtVisibility(currentDisease, null); + } + + ctCqValueComponent.updateCqVisibility(currentDisease, currentTestType, currentTestResult); + syncSelfVisibility(); + })); + + // Listen for test result changes + track(eventBus.on(TestResultChangedEvent.class, event -> { + currentTestResult = event.getTestResult(); + ctCqValueComponent.updateCqVisibility(currentDisease, currentTestType, currentTestResult); + syncSelfVisibility(); + })); + + // Listen for disease changes + track(eventBus.on(DiseaseChangedEvent.class, event -> { + currentDisease = event.getDisease(); + if (currentTestType != null) { + updateFourFoldIncrease(currentTestType); + } + ctCqValueComponent.updateCtVisibility(currentDisease, currentTestType); + ctCqValueComponent.updateCqVisibility(currentDisease, currentTestType, currentTestResult); + syncSelfVisibility(); + })); + } + + private void updateFourFoldIncrease(PathogenTestType testType) { + if (currentDisease == Disease.DENGUE && testType == PathogenTestType.IGG_SERUM_ANTIBODY) { + fourFoldIncrease.setCaption(I18nProperties.getCaption(Captions.PathogenTest_fourFoldIncreaseAntibodyTiter_DENGUE)); + fourFoldIncrease.setVisible(true); + fourFoldIncrease.setEnabled(true); + } else if (testType == PathogenTestType.IGM_SERUM_ANTIBODY || testType == PathogenTestType.IGG_SERUM_ANTIBODY) { + fourFoldIncrease.setCaption( + I18nProperties.getPrefixCaption(PathogenTestDto.I18N_PREFIX, PathogenTestDto.FOUR_FOLD_INCREASE_ANTIBODY_TITER)); + fourFoldIncrease.setVisible(true); + fourFoldIncrease.setEnabled(caseSampleCount >= 2); + } else { + fourFoldIncrease.setCaption( + I18nProperties.getPrefixCaption(PathogenTestDto.I18N_PREFIX, PathogenTestDto.FOUR_FOLD_INCREASE_ANTIBODY_TITER)); + fourFoldIncrease.setVisible(false); + fourFoldIncrease.setEnabled(false); + } + } + + private void syncSelfVisibility() { + setVisible(fourFoldIncrease.isVisible() || ctCqValueComponent.isVisible()); + } + + @Override + public void setDto(PathogenTestDto dto) { + super.setDto(dto); + ctCqValueComponent.setDto(dto); + // Sync visibility to the loaded DTO values, since events are not fired on initial load + currentTestType = dto != null ? dto.getTestType() : null; + currentTestResult = dto != null ? dto.getTestResult() : null; + ctCqValueComponent.updateCtVisibility(currentDisease, currentTestType); + ctCqValueComponent.updateCqVisibility(currentDisease, currentTestType, currentTestResult); + if (currentTestType != null) { + updateFourFoldIncrease(currentTestType); + } + syncSelfVisibility(); + } + + @Override + public void validate() { + super.validate(); + ctCqValueComponent.validate(); + } + + @Override + public void applyVisibility(FieldVisibilityCheckers checkers, Class dtoClass) { + super.applyVisibility(checkers, dtoClass); + ctCqValueComponent.applyVisibility(checkers, dtoClass); + syncSelfVisibility(); + } + + @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/components/PrescriberComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/PrescriberComponent.java new file mode 100644 index 00000000000..8c7e5e37afa --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/PrescriberComponent.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * 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.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. + * as a standalone composable component + */ +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/ResultTextComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/ResultTextComponent.java new file mode 100644 index 00000000000..81ad3c367f2 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/ResultTextComponent.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * 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.components; + +import com.vaadin.ui.TextArea; + +import de.symeda.sormas.api.sample.PathogenTestDto; +import de.symeda.sormas.ui.utils.FormComponent; + +/** + * Test result free-text field. + * Vaadin 8 components with own Binder, self-managed visibility. + */ +public class ResultTextComponent extends FormComponent { + + private static final long serialVersionUID = 1L; + + private TextArea resultText; + + public ResultTextComponent() { + super(PathogenTestDto.class); + buildLayout(); + bindFields(); + } + + private void buildLayout() { + resultText = createTextArea(PathogenTestDto.TEST_RESULT_TEXT, PathogenTestDto.I18N_PREFIX); + resultText.setRows(6); + addFullWidthRow(resultText); + } + + private void bindFields() { + binder.forField(resultText).bind(PathogenTestDto::getTestResultText, PathogenTestDto::setTestResultText); + } +} 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..561ee80b986 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/TestIdentificationComponent.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * 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.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. + * as a standalone composable component + */ +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..b6d8bd0d14f --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/TestMethodComponent.java @@ -0,0 +1,351 @@ +/******************************************************************************* + * 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.components; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +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.PCRTestSpecification; +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. + * as a standalone composable component + */ +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 ComboBox pcrTestSpecField; + private HorizontalLayout pcrTestSpecRow; + private TextField typingIdField; + private DateField testDateField; + private ComboBox testTimeField; + private ComboBox labField; + private TextField labDetailsField; + + private PathogenTestDto currentDto; + private Disease currentDisease; + + private HorizontalLayout typingIdRow; + private HorizontalLayout labDetailsRow; + + private Supplier testDateSampleDateSupplier; + private String testDateValidationError; + + public TestMethodComponent(FormEventBus eventBus, Supplier sampleDateSupplier, Disease initialDisease) { + super(PathogenTestDto.class); + this.eventBus = eventBus; + this.sampleDateSupplier = sampleDateSupplier; + this.currentDisease = initialDisease; + buildLayout(); + bindFields(); + wireEvents(); + } + + private void buildLayout() { + // Test type + testTypeField = createComboBox(PathogenTestDto.TEST_TYPE, PathogenTestDto.I18N_PREFIX); + updateComboBoxByDisease(testTypeField, PathogenTestType.class, currentDisease); + testTypeField.setItemCaptionGenerator(PathogenTestType::toString); + + testTypeTextField = createTextField(PathogenTestDto.TEST_TYPE_TEXT, PathogenTestDto.I18N_PREFIX, ValueChangeMode.BLUR); + testTypeTextField.setVisible(false); + + testTypeTextSpacer = createSpacer(); + addToggleRow(testTypeField, testTypeTextField, testTypeTextSpacer); + + // PCR test specification (Coronavirus-specific, below test type) + pcrTestSpecField = createComboBox(PathogenTestDto.PCR_TEST_SPECIFICATION, PathogenTestDto.I18N_PREFIX); + pcrTestSpecField.setItems(PCRTestSpecification.values()); + pcrTestSpecField.setItemCaptionGenerator(PCRTestSpecification::toString); + pcrTestSpecField.setVisible(false); + pcrTestSpecRow = addRow(pcrTestSpecField); + + // 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(pcrTestSpecField).bind(PathogenTestDto::getPcrTestSpecification, PathogenTestDto::setPcrTestSpecification); + 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); + + updatePcrTestSpecVisibility(type); + + 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 and PCR spec visibility + track(eventBus.on(DiseaseChangedEvent.class, event -> { + currentDisease = event.getDisease(); + updateTestTypeItems(currentDisease); + updatePcrTestSpecVisibility(testTypeField.getValue()); + })); + } + + 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); + } + + private void updatePcrTestSpecVisibility(PathogenTestType testType) { + boolean visible = currentDisease == Disease.CORONAVIRUS && testType == PathogenTestType.PCR_RT_PCR; + pcrTestSpecField.setVisible(visible); + if (!visible) { + pcrTestSpecField.clear(); + } + updateRowVisibility(pcrTestSpecRow); + } + + /** Forces typingId field visible when a preset value exists, regardless of current test type. */ + public void showTypingIdIfPreset(String typingId) { + if (typingId != null && !typingId.trim().isEmpty()) { + typingIdField.setVisible(true); + updateRowVisibility(typingIdRow); + } + } + + /** + * Registers a validator ensuring test date is not before sample date. + * Evaluated during {@link #validate()}. + */ + public void addTestDateAfterSampleDateValidator(Supplier sampleDateSupplier, String errorMessage) { + this.testDateSampleDateSupplier = sampleDateSupplier; + this.testDateValidationError = errorMessage; + } + + @Override + public void validate() { + super.validate(); + if (testDateSampleDateSupplier != null && testDateField.getValue() != null) { + Date sampleDate = testDateSampleDateSupplier.get(); + if (sampleDate != null) { + Integer totalMinutes = testTimeField.getValue(); + LocalDateTime ldt = totalMinutes != null + ? testDateField.getValue().atTime(totalMinutes / 60, totalMinutes % 60) + : testDateField.getValue().atStartOfDay(); + Date testDate = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant()); + if (testDate.before(sampleDate)) { + throw new com.vaadin.v7.data.Validator.InvalidValueException(testDateValidationError); + } + } + } + } + + 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..e2c5b5af17c --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/components/TestResultComponent.java @@ -0,0 +1,154 @@ +/******************************************************************************* + * 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.components; + +import java.util.ArrayList; +import java.util.List; + +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.RadioButtonGroup; + +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.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, and preliminary fields. + * as a standalone composable component + */ +public class TestResultComponent extends FormComponent { + + private static final long serialVersionUID = 1L; + + private final FormEventBus eventBus; + private final boolean isLuxembourg; + + private ComboBox testResultField; + private RadioButtonGroup testResultVerifiedField; + private RadioButtonGroup preliminaryField; + + private Disease currentDisease; + private PathogenTestType currentTestType; + + public TestResultComponent(FormEventBus eventBus, boolean isLuxembourg, Disease initialDisease) { + super(PathogenTestDto.class); + this.eventBus = eventBus; + this.isLuxembourg = isLuxembourg; + this.currentDisease = initialDisease; + buildLayout(); + bindFields(); + wireEvents(); + } + + private void buildLayout() { + 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); + + testResultVerifiedField = createBooleanRadioGroup(PathogenTestDto.TEST_RESULT_VERIFIED, PathogenTestDto.I18N_PREFIX); + + 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); + } + + 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); + } + + private void wireEvents() { + // Test result changed -> fire event + track(testResultField.addValueChangeListener(e -> { + eventBus.fire(new TestResultChangedEvent(e.getValue())); + })); + + // Listen for test type changes -> clear result when type is cleared + track(eventBus.on(TestTypeChangedEvent.class, event -> { + currentTestType = event.getTestType(); + if (currentTestType == null) { + testResultField.clear(); + testResultField.setEnabled(true); + } + })); + + // 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(); + } + })); + + // Listen for disease changes -> clear result if disease changed + track(eventBus.on(DiseaseChangedEvent.class, event -> { + Disease oldDisease = currentDisease; + currentDisease = event.getDisease(); + if (currentDisease != oldDisease && currentTestType != null) { + testResultField.clear(); + } + })); + + // 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); + } + } +} 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..d26228bff41 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/AbstractDiseaseSectionComponent.java @@ -0,0 +1,146 @@ +/******************************************************************************* + * 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 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/CryptosporidiosisSectionComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CryptosporidiosisSectionComponent.java new file mode 100644 index 00000000000..fd3d2c4f5ca --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CryptosporidiosisSectionComponent.java @@ -0,0 +1,114 @@ +/******************************************************************************* + * 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 com.vaadin.ui.ComboBox; +import com.vaadin.ui.Label; +import com.vaadin.ui.TextField; + +import de.symeda.sormas.api.sample.GenoType; +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 CryptosporidiosisSectionComponent extends AbstractDiseaseSectionComponent { + + private ComboBox genoTypeField; + private TextField genoTypeTextField; + private Label genoTypeTextFieldSpacer; + + private PathogenTestType currentTestType; + private PathogenTestResultType currentResult; + + @Override + protected void buildLayout() { + genoTypeField = createComboBox(PathogenTestDto.GENOTYPE); + genoTypeField.setItemCaptionGenerator(GenoType::toString); + genoTypeField.setVisible(false); + updateGenoTypeItems(); + + genoTypeTextField = createTextField(PathogenTestDto.GENOTYPE_TEXT); + genoTypeTextField.setVisible(false); + + genoTypeTextFieldSpacer = createSpacer(); + addToggleRow(genoTypeField, genoTypeTextField, genoTypeTextFieldSpacer); + + binder.forField(genoTypeField).bind(PathogenTestDto::getGenoType, PathogenTestDto::setGenoType); + binder.forField(genoTypeTextField).bind(PathogenTestDto::getGenoTypeText, PathogenTestDto::setGenoTypeText); + } + + @Override + protected void wireVisibility() { + track(genoTypeField.addValueChangeListener(e -> { + boolean showText = e.getValue() == GenoType.OTHER && genoTypeField.isVisible(); + genoTypeTextField.setVisible(showText); + genoTypeTextFieldSpacer.setVisible(!showText); + if (!showText) { + genoTypeTextField.clear(); + } + })); + + track(eventBus.on(TestTypeChangedEvent.class, event -> { + currentTestType = event.getTestType(); + updateVisibility(); + updateGenoTypeItems(); + + // 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; + genoTypeField.setVisible(visible); + if (!visible) { + genoTypeField.clear(); + genoTypeTextField.setVisible(false); + genoTypeTextField.clear(); + } + updateRowAndSelfVisibility(); + } + + @Override + protected void clearOwnedFields() { + PathogenTestDto dto = binder.getBean(); + if (dto == null) { + return; + } + dto.setGenoType(null); + dto.setGenoTypeText(null); + } + + private void updateGenoTypeItems() { + updateComboBoxByDisease(genoTypeField, GenoType.class, disease); + } + +} 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..0e47c99275e --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/CsmSectionComponent.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * 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 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_TEXT); + serotypeField.setVisible(false); + addRow(serotypeField, createSpacer()); + + binder.forField(serotypeField).bind(PathogenTestDto::getSerotypeText, PathogenTestDto::setSerotypeText); + } + + @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.setSerotypeText(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..e277d684d8c --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/DefaultSectionComponent.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * 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 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/DengueSectionComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/DengueSectionComponent.java new file mode 100644 index 00000000000..3b50a627ec3 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/DengueSectionComponent.java @@ -0,0 +1,133 @@ +/******************************************************************************* + * 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 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.sample.PathogenTestDto; +import de.symeda.sormas.api.sample.PathogenTestResultType; +import de.symeda.sormas.api.sample.PathogenTestType; +import de.symeda.sormas.api.sample.Serotype; +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 DengueSectionComponent extends AbstractDiseaseSectionComponent { + + private static final List SEROTYPE_VISIBLE_TYPES = Arrays.asList( + PathogenTestType.NAAT, + PathogenTestType.PCR_RT_PCR, + PathogenTestType.NEUTRALIZING_ANTIBODIES); + + private static final List AUTO_POSITIVE_TYPES = Arrays.asList( + PathogenTestType.NAAT, + PathogenTestType.NEUTRALIZING_ANTIBODIES, + PathogenTestType.PCR_RT_PCR); + + private ComboBox serotypeField; + private TextField serotypeTextField; + private Label serotypeTextSpacer; + private TextField antibodyTitreField; + + private PathogenTestType currentTestType; + private PathogenTestResultType currentResult; + + @Override + protected void buildLayout() { + serotypeField = createComboBox(PathogenTestDto.SEROTYPE); + serotypeField.setItemCaptionGenerator(Serotype::toString); + serotypeField.setVisible(false); + updateComboBoxByDisease(serotypeField, Serotype.class, disease); + + serotypeTextField = createTextField(PathogenTestDto.SEROTYPE_TEXT); + serotypeTextField.setVisible(false); + + serotypeTextSpacer = createSpacer(); + addToggleRow(serotypeField, serotypeTextField, serotypeTextSpacer); + + antibodyTitreField = createTextField(PathogenTestDto.ANTIBODY_TITRE); + antibodyTitreField.setVisible(false); + addRow(antibodyTitreField, createSpacer()); + + binder.forField(serotypeField).bind(PathogenTestDto::getSerotype, PathogenTestDto::setSerotype); + binder.forField(serotypeTextField).bind(PathogenTestDto::getSerotypeText, PathogenTestDto::setSerotypeText); + binder.forField(antibodyTitreField).bind(PathogenTestDto::getAntibodyTitre, PathogenTestDto::setAntibodyTitre); + } + + @Override + protected void wireVisibility() { + track(serotypeField.addValueChangeListener(e -> { + boolean showText = e.getValue() == Serotype.OTHER && serotypeField.isVisible(); + serotypeTextField.setVisible(showText); + serotypeTextSpacer.setVisible(!showText); + if (!showText) { + serotypeTextField.clear(); + } + })); + + track(eventBus.on(TestTypeChangedEvent.class, event -> { + currentTestType = event.getTestType(); + updateVisibility(); + + 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 showSerotype = SEROTYPE_VISIBLE_TYPES.contains(currentTestType); + serotypeField.setVisible(showSerotype); + if (!showSerotype) { + serotypeField.clear(); + serotypeTextField.setVisible(false); + serotypeTextField.clear(); + } + + boolean showAntibodyTitre = currentTestType == PathogenTestType.NEUTRALIZING_ANTIBODIES; + antibodyTitreField.setVisible(showAntibodyTitre); + if (!showAntibodyTitre) { + antibodyTitreField.clear(); + } + + updateRowAndSelfVisibility(); + } + + @Override + protected void clearOwnedFields() { + PathogenTestDto dto = binder.getBean(); + if (dto == null) { + return; + } + dto.setSerotype(null); + dto.setSerotypeText(null); + dto.setAntibodyTitre(null); + } +} 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..493fcfc1d6f --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/DiseaseSectionFactory.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * 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.Disease; + +/** + * Factory for component-based disease sections. + */ +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 DENGUE: + return new DengueSectionComponent(); + case MALARIA: + return new MalariaSectionComponent(); + 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..e79bdebb0d7 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/ImiSectionComponent.java @@ -0,0 +1,155 @@ +/******************************************************************************* + * 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 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() { + // seroGroupSpecText visible only when OTHER + track(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); + } + + 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); + dto.setDrugSusceptibility(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..06831d4b346 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/IpiSectionComponent.java @@ -0,0 +1,166 @@ +/******************************************************************************* + * 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 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_TEXT); + 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::getSerotypeText, PathogenTestDto::setSerotypeText); + 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() { + // serotypingMethodText visible only when OTHER + track(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); + } + + 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.setSerotypeText(null); + dto.setSeroTypingMethod(null); + dto.setSeroTypingMethodText(null); + dto.setDrugSusceptibility(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/MalariaSectionComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/MalariaSectionComponent.java new file mode 100644 index 00000000000..407ea9c08ed --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/MalariaSectionComponent.java @@ -0,0 +1,146 @@ +/******************************************************************************* + * 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 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.sample.PathogenSpecie; +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.TestTypeChangedEvent; + +public class MalariaSectionComponent extends AbstractDiseaseSectionComponent { + + private static final List SPECIE_VISIBLE_TYPES = Arrays.asList( + PathogenTestType.THIN_BLOOD_SMEAR, + PathogenTestType.ANTIGEN_DETECTION, + PathogenTestType.RAPID_TEST, + PathogenTestType.PCR_RT_PCR, + PathogenTestType.Q_PCR, + PathogenTestType.LAMP, + PathogenTestType.INDIRECT_FLUORESCENT_ANTIBODY, + PathogenTestType.OTHER_MOLECULAR_ASSAY, + PathogenTestType.OTHER_SEROLOGICAL_TEST, + PathogenTestType.OTHER_ANTIGEN_DETECTION_TEST, + PathogenTestType.ENZYME_LINKED_IMMUNOSORBENT_ASSAY); + + private static final List AUTO_POSITIVE_TYPES = Arrays.asList( + PathogenTestType.ANTIGEN_DETECTION, + PathogenTestType.THIN_BLOOD_SMEAR, + PathogenTestType.RAPID_TEST, + PathogenTestType.INDIRECT_FLUORESCENT_ANTIBODY, + PathogenTestType.PCR_RT_PCR, + PathogenTestType.Q_PCR, + PathogenTestType.ENZYME_LINKED_IMMUNOSORBENT_ASSAY, + PathogenTestType.LAMP, + PathogenTestType.OTHER_ANTIGEN_DETECTION_TEST, + PathogenTestType.OTHER_SEROLOGICAL_TEST, + PathogenTestType.OTHER_MOLECULAR_ASSAY); + + private static final List RESULT_DETAILS_VISIBLE_TYPES = + Arrays.asList(PathogenTestType.THIN_BLOOD_SMEAR, PathogenTestType.Q_PCR); + + private ComboBox specieField; + private TextField specieTextField; + private Label specieTextSpacer; + private TextField resultDetailsField; + + private PathogenTestType currentTestType; + + @Override + protected void buildLayout() { + specieField = createComboBox(PathogenTestDto.SPECIE); + specieField.setItemCaptionGenerator(PathogenSpecie::toString); + specieField.setVisible(false); + updateComboBoxByDiseaseAndTestType(specieField, PathogenSpecie.class, disease, currentTestType); + + specieTextField = createTextField(PathogenTestDto.SPECIE_TEXT); + specieTextField.setVisible(false); + + specieTextSpacer = createSpacer(); + addToggleRow(specieField, specieTextField, specieTextSpacer); + + resultDetailsField = createTextField(PathogenTestDto.RESULT_DETAILS); + resultDetailsField.setVisible(false); + addRow(resultDetailsField, createSpacer()); + + binder.forField(specieField).bind(PathogenTestDto::getSpecie, PathogenTestDto::setSpecie); + binder.forField(specieTextField).bind(PathogenTestDto::getSpecieText, PathogenTestDto::setSpecieText); + binder.forField(resultDetailsField).bind(PathogenTestDto::getResultDetails, PathogenTestDto::setResultDetails); + } + + @Override + protected void wireVisibility() { + track(specieField.addValueChangeListener(e -> { + boolean showText = e.getValue() == PathogenSpecie.OTHER && specieField.isVisible(); + specieTextField.setVisible(showText); + specieTextSpacer.setVisible(!showText); + if (!showText) { + specieTextField.clear(); + } + })); + + track(eventBus.on(TestTypeChangedEvent.class, event -> { + currentTestType = event.getTestType(); + updateComboBoxByDiseaseAndTestType(specieField, PathogenSpecie.class, disease, currentTestType); + updateVisibility(); + + if (currentTestType != null && AUTO_POSITIVE_TYPES.contains(currentTestType)) { + eventBus.fire(new SetTestResultEvent(PathogenTestResultType.POSITIVE)); + } else if (currentTestType != null) { + eventBus.fire(new SetTestResultEvent(null)); + } + })); + } + + private void updateVisibility() { + boolean showSpecie = SPECIE_VISIBLE_TYPES.contains(currentTestType); + specieField.setVisible(showSpecie); + if (!showSpecie) { + specieField.clear(); + specieTextField.setVisible(false); + specieTextField.clear(); + } + + boolean showResultDetails = RESULT_DETAILS_VISIBLE_TYPES.contains(currentTestType); + resultDetailsField.setVisible(showResultDetails); + if (!showResultDetails) { + resultDetailsField.clear(); + } + + updateRowAndSelfVisibility(); + } + + @Override + protected void clearOwnedFields() { + PathogenTestDto dto = binder.getBean(); + if (dto == null) { + return; + } + dto.setSpecie(null); + dto.setSpecieText(null); + dto.setResultDetails(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..feba82c32a6 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/MeaslesSectionComponent.java @@ -0,0 +1,115 @@ +/******************************************************************************* + * 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 com.vaadin.ui.ComboBox; +import com.vaadin.ui.Label; +import com.vaadin.ui.TextField; + +import de.symeda.sormas.api.sample.GenoType; +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 genoTypeField; + private TextField genoTypeTextField; + private Label genoTypeTextSpacer; + + private PathogenTestType currentTestType; + private PathogenTestResultType currentResult; + + @Override + protected void buildLayout() { + genoTypeField = createComboBox(PathogenTestDto.GENOTYPE); + genoTypeField.setItemCaptionGenerator(GenoType::toString); + genoTypeField.setVisible(false); + updateGenoTypeItems(); + + genoTypeTextField = createTextField(PathogenTestDto.GENOTYPE_TEXT); + genoTypeTextField.setVisible(false); + + genoTypeTextSpacer = createSpacer(); + addToggleRow(genoTypeField, genoTypeTextField, genoTypeTextSpacer); + + binder.forField(genoTypeField).bind(PathogenTestDto::getGenoType, PathogenTestDto::setGenoType); + binder.forField(genoTypeTextField).bind(PathogenTestDto::getGenoTypeText, PathogenTestDto::setGenoTypeText); + } + + @Override + protected void wireVisibility() { + // genoTypeResultText visible only when OTHER selected + track(genoTypeField.addValueChangeListener(e -> { + boolean showText = e.getValue() == GenoType.OTHER && genoTypeField.isVisible(); + genoTypeTextField.setVisible(showText); + genoTypeTextSpacer.setVisible(!showText); + if (!showText) { + genoTypeTextField.clear(); + } + })); + + track(eventBus.on(TestTypeChangedEvent.class, event -> { + currentTestType = event.getTestType(); + updateVisibility(); + updateGenoTypeItems(); + + // 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; + genoTypeField.setVisible(visible); + if (!visible) { + genoTypeField.clear(); + genoTypeTextField.setVisible(false); + genoTypeTextField.clear(); + } + updateRowAndSelfVisibility(); + } + + @Override + protected void clearOwnedFields() { + PathogenTestDto dto = binder.getBean(); + if (dto == null) { + return; + } + dto.setGenoType(null); + dto.setGenoTypeText(null); + } + + private void updateGenoTypeItems() { + updateComboBoxByDisease(genoTypeField, GenoType.class, disease); + } + +} 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/diseasesection/TuberculosisSectionComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/TuberculosisSectionComponent.java new file mode 100644 index 00000000000..8bc535d62a3 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/diseasesection/TuberculosisSectionComponent.java @@ -0,0 +1,376 @@ + +/******************************************************************************* + * 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 java.util.Locale; + +import com.vaadin.data.ValueProvider; +import com.vaadin.server.Setter; +import com.vaadin.shared.ui.ValueChangeMode; +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.setItemCaptionGenerator(PathogenSpecie::toString); + specie.setVisible(false); + updateComboBoxByDiseaseAndTestType(specie, PathogenSpecie.class, disease, currentTestType); + 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) { + TextField field = createTextField(propertyId); + field.setValueChangeMode(ValueChangeMode.LAZY); + field.setValueChangeTimeout(1000); + return field; + } + + @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(); + } + + updateComboBoxByDiseaseAndTestType(specie, PathogenSpecie.class, disease, currentTestType); + 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); + dto.setDrugSusceptibility(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); + + track(tubeField.addValueChangeListener(e -> { + Locale locale = tubeField.getLocale() != null ? tubeField.getLocale() : Locale.getDefault(); + StringToFloatNullableConverter.parseLocaleAware(e.getValue(), locale).ifPresent(f -> gt10Field.setValue(f > 10)); + })); + + track(gt10Field.addValueChangeListener(e -> { + Locale locale = tubeField.getLocale() != null ? tubeField.getLocale() : Locale.getDefault(); + if (Boolean.TRUE.equals(e.getValue())) { + StringToFloatNullableConverter.parseLocaleAware(tubeField.getValue(), locale).filter(f -> f <= 10).ifPresent(f -> tubeField.clear()); + } else if (Boolean.FALSE.equals(e.getValue())) { + StringToFloatNullableConverter.parseLocaleAware(tubeField.getValue(), locale).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/samples/humansample/SampleController.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/humansample/SampleController.java index 3e4135575a5..c1c893e8c57 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/humansample/SampleController.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/samples/humansample/SampleController.java @@ -22,7 +22,6 @@ import java.util.function.Consumer; import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; import com.vaadin.navigator.Navigator; import com.vaadin.server.Sizeable.Unit; @@ -40,9 +39,6 @@ import com.vaadin.ui.themes.ValoTheme; import com.vaadin.v7.data.Buffered.SourceException; import com.vaadin.v7.data.Validator.InvalidValueException; -import com.vaadin.v7.ui.CheckBox; -import com.vaadin.v7.ui.ComboBox; -import com.vaadin.v7.ui.Field; import de.symeda.sormas.api.Disease; import de.symeda.sormas.api.FacadeProvider; @@ -89,7 +85,6 @@ import de.symeda.sormas.ui.utils.CommitDiscardWrapperComponent; import de.symeda.sormas.ui.utils.ConfirmationComponent; 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.DeleteRestoreHandlers; @@ -221,39 +216,29 @@ private CollapsiblePathogenTestForm addPathogenTestComponent( if (pathogenTest != null) { pathogenTestForm.setValue(pathogenTest); // show typingId field when it has a preset value - if (StringUtils.isNotBlank(pathogenTest.getTypingId())) { - pathogenTestForm.getField(PathogenTestDto.TYPING_ID).setVisible(true); - } + pathogenTestForm.showTypingIdIfPreset(pathogenTest.getTypingId()); } else { pathogenTestForm.setValue(PathogenTestDto.build(sampleComponent.getWrappedComponent().getValue(), UiUtil.getUser())); - // remove value invalid for newly created pathogen tests - ComboBox pathogenTestResultField = pathogenTestForm.getField(PathogenTestDto.TEST_RESULT); - pathogenTestResultField.removeItem(PathogenTestResultType.NOT_DONE); - pathogenTestResultField.setValue(PathogenTestResultType.PENDING); - ComboBox testDiseaseField = pathogenTestForm.getField(PathogenTestDto.TESTED_DISEASE); - // setting the disease field value is only necessary if the disease is not null - testDiseaseField.setValue(pathogenTestFormDisease); + // set initial result and disease for newly created pathogen tests + pathogenTestForm.initializeForNewTest(pathogenTestFormDisease); } // setup field updates - Field testLabField = pathogenTestForm.getField(PathogenTestDto.LAB); NullableOptionGroup samplePurposeField = sampleComponent.getWrappedComponent().getField(SampleDto.SAMPLE_PURPOSE); - Runnable updateTestLabFieldRequired = () -> testLabField.setRequired(!SamplePurpose.INTERNAL.equals(samplePurposeField.getValue())); - updateTestLabFieldRequired.run(); - samplePurposeField.addValueChangeListener(e -> updateTestLabFieldRequired.run()); + Runnable updateLabRequired = () -> pathogenTestForm.setLabRequired(!SamplePurpose.INTERNAL.equals(samplePurposeField.getValue())); + updateLabRequired.run(); + samplePurposeField.addValueChangeListener(e -> updateLabRequired.run()); // validate pathogen test create component before saving the sample sampleComponent.addFieldGroups(pathogenTestForm.getFieldGroup()); - // Sample creation specific configuration + // Sample creation specific configuration: test date must not be before sample date final DateTimeField sampleDateField = sampleComponent.getWrappedComponent().getField(SampleDto.SAMPLE_DATE_TIME); - final DateTimeField testDateField = pathogenTestForm.getField(PathogenTestDto.TEST_DATE_TIME); - testDateField.addValidator( - new DateComparisonValidator( - testDateField, - sampleDateField, - false, - false, - I18nProperties.getValidationError(Validations.afterDate, testDateField.getCaption(), sampleDateField.getCaption()))); + pathogenTestForm.addTestDateAfterSampleDateValidator( + sampleDateField::getValue, + I18nProperties.getValidationError( + Validations.afterDate, + I18nProperties.getPrefixCaption(PathogenTestDto.I18N_PREFIX, PathogenTestDto.TEST_DATE_TIME), + sampleDateField.getCaption())); if (viaLims) { setViaLimsFieldChecked(pathogenTestForm); @@ -345,8 +330,7 @@ public void addPathogenTestButton( } public void setViaLimsFieldChecked(PathogenTestForm pathogenTestForm) { - CheckBox viaLimsCheckbox = pathogenTestForm.getField(PathogenTestDto.VIA_LIMS); - viaLimsCheckbox.setValue(Boolean.TRUE); + pathogenTestForm.setViaLims(true); } public void createReferral(SampleDto existingSample, Disease disease) { 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