diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/dashboard/BaseDashboardCriteria.java b/sormas-api/src/main/java/de/symeda/sormas/api/dashboard/BaseDashboardCriteria.java index 3fe408d7928..cf51e2d6bf3 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/dashboard/BaseDashboardCriteria.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/dashboard/BaseDashboardCriteria.java @@ -22,6 +22,7 @@ import de.symeda.sormas.api.infrastructure.district.DistrictReferenceDto; import de.symeda.sormas.api.infrastructure.region.RegionReferenceDto; import de.symeda.sormas.api.utils.criteria.BaseCriteria; +import de.symeda.sormas.api.caze.NewCaseDateType; public class BaseDashboardCriteria> extends BaseCriteria { @@ -34,6 +35,7 @@ public class BaseDashboardCriteria> extend private Date dateTo; private Date previousDateFrom; private Date previousDateTo; + protected NewCaseDateType dateTypeClass; protected BaseDashboardCriteria(final Class selfClass) { self = selfClass.cast(this); diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/dashboard/DashboardCriteria.java b/sormas-api/src/main/java/de/symeda/sormas/api/dashboard/DashboardCriteria.java index 41aebd5b14a..3efd57cce50 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/dashboard/DashboardCriteria.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/dashboard/DashboardCriteria.java @@ -2,6 +2,9 @@ import de.symeda.sormas.api.CaseMeasure; import de.symeda.sormas.api.utils.criteria.CriteriaDateType; +import de.symeda.sormas.api.caze.CaseClassification; +import de.symeda.sormas.api.caze.CaseOutcome; +import de.symeda.sormas.api.caze.NewCaseDateType; public class DashboardCriteria extends BaseDashboardCriteria { @@ -10,6 +13,9 @@ public class DashboardCriteria extends BaseDashboardCriteria private boolean showMinimumEntries; private CaseMeasure caseMeasure; private boolean includeNotACaseClassification; + private CaseClassification caseClassification; + private NewDateFilterType dateFilterType; + private CaseOutcome outcome; public DashboardCriteria() { super(DashboardCriteria.class); @@ -48,4 +54,42 @@ public boolean isShowMinimumEntries() { public CaseMeasure getCaseMeasure() { return caseMeasure; } + + public CaseClassification getCaseClassification() { + + return caseClassification; + } + + public DashboardCriteria caseClassification(CaseClassification caseClassification) { + + this.caseClassification = caseClassification; + return this; + } + + public NewDateFilterType getDateFilterType() { + + return dateFilterType; + } + + public DashboardCriteria dateFilterType(NewDateFilterType dateFilterType) { + + this.dateFilterType = dateFilterType; + return this; + } + + public void setOutcome(CaseOutcome outcome) { + + this.outcome = outcome; + } + + public CaseOutcome getOutcome() { + + return outcome; + } + + public DashboardCriteria setDateTypeClass(NewCaseDateType dateTypeClass) { + + this.dateTypeClass = dateTypeClass; + return this; + } } diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/dashboard/DashboardFacade.java b/sormas-api/src/main/java/de/symeda/sormas/api/dashboard/DashboardFacade.java index 38e927e939b..1a5a30a92f3 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/dashboard/DashboardFacade.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/dashboard/DashboardFacade.java @@ -15,6 +15,7 @@ import de.symeda.sormas.api.person.PresentCondition; import de.symeda.sormas.api.sample.PathogenTestResultType; import de.symeda.sormas.api.utils.criteria.CriteriaDateType; +import de.symeda.sormas.api.Disease; @Remote public interface DashboardFacade { @@ -59,4 +60,28 @@ List getDiseaseBurden( Date previousFromDate, Date previousToDate, CriteriaDateType newCaseDateType); + + DiseaseBurdenDto getDiseaseForDashboard( + RegionReferenceDto regionRef, + DistrictReferenceDto districtRef, + Disease disease, + Date fromDate, + Date toDate, + Date previousFromDate, + Date previousToDate, + CriteriaDateType newCaseDateType, + CaseClassification caseClassification + ); + + DiseaseBurdenDto getDiseaseGridForDashboard( + RegionReferenceDto regionRef, + DistrictReferenceDto districtRef, + Disease disease, + Date from, + Date to, + Date previousFromDate, + Date previousToDate, + CriteriaDateType newCaseDateType, + CaseClassification caseClassification + ); } diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/dashboard/NewDateFilterType.java b/sormas-api/src/main/java/de/symeda/sormas/api/dashboard/NewDateFilterType.java new file mode 100644 index 00000000000..9b15d4d269d --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/dashboard/NewDateFilterType.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * 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.api.dashboard; + +public enum NewDateFilterType { + TODAY, + YESTERDAY, + THIS_WEEK, + LAST_WEEK, + THIS_YEAR, + CUSTOM; +} \ No newline at end of file diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/disease/DiseaseBurdenDto.java b/sormas-api/src/main/java/de/symeda/sormas/api/disease/DiseaseBurdenDto.java index 0e6ad2f6ed1..6503d5032c7 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/disease/DiseaseBurdenDto.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/disease/DiseaseBurdenDto.java @@ -20,6 +20,9 @@ import java.io.Serializable; import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.caze.CaseClassification; +import de.symeda.sormas.api.infrastructure.region.RegionDto; +import java.util.Date; public class DiseaseBurdenDto implements Serializable { @@ -38,13 +41,131 @@ public class DiseaseBurdenDto implements Serializable { public static final String CASE_FATALITY_RATE = "caseFatalityRate"; public static final String LAST_REPORTED_DISTRICT_NAME = "lastReportedDistrictName"; + //Regional specific Disease Details + public static final String CASES_TOTAL = "total"; + public static final String CASES_COUNT_TOTAL = "totalCount"; + + public static final String CASES_REGION = "region"; + public static final String CASES_DISTRICT = "district"; + + public static final String ACTIVE_CASE = "activeCases"; + public static final String ACTIVE_COUNT_CASE = "activeCount"; + + public static final String RECOVERED_CASES = "recovered"; + public static final String RECOVERED_COUNT_CASES = "recoveredCount"; + + public static final String DEATH = "deaths"; + public static final String DEATH_COUNT = "deathsCount"; + + public static final String OTHER = "other"; + public static final String OTHER_COUNT = "otherCount"; + private Disease disease; + private String total; + private String totalCount; + private Long caseCount; private Long previousCaseCount; private Long eventCount; private Long outbreakDistrictCount; private Long caseDeathCount; private String lastReportedDistrictName; + private CaseClassification caseClassification; + + private Integer cfr; + private String lastReportedDistrict; + private String outbreakDistrict; + + private String deaths; + private String deathsCount; + + private RegionDto region; + + private String recovered; + private String recoveredCount; + + private String activeCases; + private String activeCount; + + private String other; + private String otherCount; + + private Date toDate; + private Date fromDate; + + public DiseaseBurdenDto(RegionDto regionDto, String total, String activeCases, String recovered, String deaths, String other) { + + this.region = regionDto; + this.total = total; + this.activeCases = activeCases; + this.recovered = recovered; + this.deaths = deaths; + this.other = other; + } + + public DiseaseBurdenDto( + Disease disease, + Long caseCount, + Long previousCaseCount, + Long eventCount, + Long outbreakDistrictCount, + Long caseDeathCount, + String lastReportedDistrictName, + String outbreakDistrict) { + + this.disease = disease; + this.caseCount = caseCount; + this.previousCaseCount = previousCaseCount; + this.eventCount = eventCount; + this.outbreakDistrictCount = outbreakDistrictCount; + this.caseDeathCount = caseDeathCount; + this.lastReportedDistrictName = lastReportedDistrictName; + this.outbreakDistrict = outbreakDistrict; + } + + public DiseaseBurdenDto( + Disease disease, + Long caseCount, + Long previousCaseCount, + Long eventCount, + Long outbreakDistrictCount, + Long caseDeathCount, + String lastReportedDistrictName, + String outbreakDistrict, + Date from, + Date to) { + + this.disease = disease; + this.caseCount = caseCount; + this.previousCaseCount = previousCaseCount; + this.eventCount = eventCount; + this.outbreakDistrictCount = outbreakDistrictCount; + this.caseDeathCount = caseDeathCount; + this.lastReportedDistrictName = lastReportedDistrictName; + this.outbreakDistrict = outbreakDistrict; + this.fromDate = from; + this.toDate = to; + } + + public DiseaseBurdenDto( + Disease disease, + Long caseCount, + Long previousCaseCount, + Long eventCount, + Long outbreakDistrictCount, + Long caseDeathCount, + String lastReportedDistrictName, + CaseClassification caseClassification) { + + this.disease = disease; + this.caseCount = caseCount; + this.previousCaseCount = previousCaseCount; + this.eventCount = eventCount; + this.outbreakDistrictCount = outbreakDistrictCount; + this.caseDeathCount = caseDeathCount; + this.lastReportedDistrictName = lastReportedDistrictName; + this.caseClassification = caseClassification; + } public DiseaseBurdenDto( Disease disease, @@ -64,6 +185,15 @@ public DiseaseBurdenDto( this.lastReportedDistrictName = lastReportedDistrictName; } + public DiseaseBurdenDto(RegionDto regionDto, String total, String activeCases, String recovered, String deaths) { + + this.region = regionDto; + this.total = total; + this.activeCases = activeCases; + this.recovered = recovered; + this.deaths = deaths; + } + public Disease getDisease() { return disease; } @@ -72,6 +202,16 @@ public void setDisease(Disease disease) { this.disease = disease; } + public CaseClassification getCaseClassification() { + + return caseClassification; + } + + public void setCaseClassification(CaseClassification caseClassification) { + + this.caseClassification = caseClassification; + } + public Long getCaseCount() { return caseCount; } @@ -147,4 +287,169 @@ public void setLastReportedDistrictName(String name) { public Boolean hasCount() { return (caseCount + previousCaseCount + eventCount + outbreakDistrictCount) > 0; } + + public Integer getCfr() { + + return cfr; + } + + public void setCfr(Integer cfr) { + + this.cfr = cfr; + } + + public String getLastReportedDistrict() { + + return lastReportedDistrict; + } + + public void setLastReportedDistrict(String lastReportedDistrict) { + + this.lastReportedDistrict = lastReportedDistrict; + } + + public String getOutbreakDistrict() { + return outbreakDistrict; + } + + public void setOutbreakDistrict(String outbreakDistrict) { + + this.outbreakDistrict = outbreakDistrict; + } + + public String getDeaths() { + return deaths; + } + + public void setDeaths(String deaths) { + + this.deaths = deaths; + } + + public RegionDto getRegion() { + + return region; + } + + public void setRegion(RegionDto region) { + + this.region = region; + } + + public String getRecovered() { + return recovered; + } + + public void setRecovered(String recovered) { + + this.recovered = recovered; + } + + public String getActiveCases() { + + return activeCases; + } + + public void setActiveCases(String activeCases) { + + this.activeCases = activeCases; + } + + public String getTotal() { + + return total; + } + + public void setTotal(String total) { + + this.total = total; + } + + public Date getToDate() { + + return toDate; + } + + public void setToDate(Date toDate) { + + this.toDate = toDate; + } + + public Date getFromDate() { + + return fromDate; + } + + public void setFromDate(Date fromDate) { + + this.fromDate = fromDate; + } + + public String getTotalCount() { + + return totalCount; + } + + public void setTotalCount(String totalCount) { + + this.totalCount = totalCount; + } + + public String getDeathsCount() { + + return deathsCount; + } + + public void setDeathsCount(String deathsCount) { + + this.deathsCount = deathsCount; + } + + public String getRecoveredCount() { + + return recoveredCount; + } + + public void setRecoveredCount(String recoveredCount) { + this.recoveredCount = recoveredCount; + } + + public String getActiveCount() { + + return activeCount; + } + + public void setActiveCount(String activeCount) { + + this.activeCount = activeCount; + } + + public String getOther() { + + return other; + } + + public void setOther(String other) { + + this.other = other; + } + + public String getOtherCount() { + + return otherCount; + } + + public void setOtherCount(String otherCount) { + + this.otherCount = otherCount; + } + + @Override + public String toString() { + return "DiseaseBurdenDto [disease=" + disease + ", total=" + total + ", caseCount=" + caseCount + ", previousCaseCount=" + previousCaseCount + + ", eventCount=" + eventCount + ", outbreakDistrictCount=" + outbreakDistrictCount + ", caseDeathCount=" + caseDeathCount + + ", lastReportedDistrictName=" + lastReportedDistrictName + ", caseClassification=" + caseClassification + ", cfr=" + cfr + + ", lastReportedDistrict=" + lastReportedDistrict + ", outbreakDistrict=" + outbreakDistrict + ", deaths=" + deaths + ", region=" + + region + ", recovered=" + recovered + ", activeCases=" + activeCases + ", toDate=" + toDate + ", fromDate=" + fromDate + "]"; + } } diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/feature/FeatureType.java b/sormas-api/src/main/java/de/symeda/sormas/api/feature/FeatureType.java index ca44b364a12..f54a6b4e65f 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/feature/FeatureType.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/feature/FeatureType.java @@ -331,7 +331,8 @@ public enum FeatureType { CASE_SURVEILANCE, CONTACT_TRACING }, null, - ImmutableMap.of(FeatureTypeProperty.S2S_SHARING, Boolean.FALSE)); + ImmutableMap.of(FeatureTypeProperty.S2S_SHARING, Boolean.FALSE)), + DISEASE_DETAILS(true, false, null, null, null); public static final FeatureType[] SURVEILLANCE_FEATURE_TYPES = { FeatureType.CASE_SURVEILANCE, diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java index cac81380ac0..298159ec21a 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java @@ -1212,6 +1212,7 @@ public interface Captions { String dashboardDiseaseCarouselSlideShow = "dashboardDiseaseCarouselSlideShow"; String dashboardDiseaseDifference = "dashboardDiseaseDifference"; String dashboardDiseaseDifferenceYAxisLabel = "dashboardDiseaseDifferenceYAxisLabel"; + String dashboardDistrictDiseaseBurden = "dashboardDistrictDiseaseBurden"; String dashboardDone = "dashboardDone"; String dashboardFacilities = "dashboardFacilities"; String dashboardFatalities = "dashboardFatalities"; @@ -1230,6 +1231,7 @@ public interface Captions { String dashboardIndeterminate = "dashboardIndeterminate"; String dashboardInvestigated = "dashboardInvestigated"; String dashboardLastReport = "dashboardLastReport"; + String dashboardLastReportedDistrict = "dashboardLastReportedDistrict"; String dashboardLastVisitGt48 = "dashboardLastVisitGt48"; String dashboardLastVisitLt24 = "dashboardLastVisitLt24"; String dashboardLastVisitLt48 = "dashboardLastVisitLt48"; @@ -1271,6 +1273,7 @@ public interface Captions { String dashboardProbable = "dashboardProbable"; String dashboardProportion = "dashboardProportion"; String dashboardReceived = "dashboardReceived"; + String dashboardRegionalDiseaseBurden = "dashboardRegionalDiseaseBurden"; String dashboardRemoved = "dashboardRemoved"; String dashboardRumor = "dashboardRumor"; String dashboardSameDayLastYear = "dashboardSameDayLastYear"; @@ -1378,6 +1381,7 @@ public interface Captions { String DiseaseBurden_eventCount = "DiseaseBurden.eventCount"; String DiseaseBurden_outbreakDistrictCount = "DiseaseBurden.outbreakDistrictCount"; String DiseaseBurden_previousCaseCount = "DiseaseBurden.previousCaseCount"; + String diseaseDetailMap = "diseaseDetailMap"; String diseaseVariantDetails = "diseaseVariantDetails"; String District = "District"; String District_archived = "District.archived"; @@ -3205,6 +3209,7 @@ public interface Captions { String View_dashboard_adverseevents = "View.dashboard.adverseevents"; String View_dashboard_campaigns = "View.dashboard.campaigns"; String View_dashboard_contacts = "View.dashboard.contacts"; + String View_dashboard_disease = "View.dashboard.disease"; String View_dashboard_samples = "View.dashboard.samples"; String View_dashboard_surveillance = "View.dashboard.surveillance"; String View_environments = "View.environments"; @@ -3238,6 +3243,7 @@ public interface Captions { String View_user_users = "View.user.users"; String View_users_sub = "View.users.sub"; String viewMessage = "viewMessage"; + String viewMore = "viewMore"; String Visit = "Visit"; String Visit_disease = "Visit.disease"; String Visit_origin = "Visit.origin"; diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/infrastructure/region/RegionDto.java b/sormas-api/src/main/java/de/symeda/sormas/api/infrastructure/region/RegionDto.java index c497ab01903..47a3204fa1d 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/infrastructure/region/RegionDto.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/infrastructure/region/RegionDto.java @@ -156,4 +156,10 @@ public String buildCaption() { public String i18nPrefix() { return I18N_PREFIX; } + + @Override + public String toString() { + + return getName(); + } } diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/infrastructure/region/RegionFacade.java b/sormas-api/src/main/java/de/symeda/sormas/api/infrastructure/region/RegionFacade.java index 4b44e5a9267..b0a7703f2b3 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/infrastructure/region/RegionFacade.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/infrastructure/region/RegionFacade.java @@ -47,4 +47,6 @@ public interface RegionFacade extends GeoLocationFacade getNamesByIds(List regionIds); boolean isUsedInOtherInfrastructureData(Collection regionUuids); + + List getAllActiveRegions(); } diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/outbreak/OutbreakCriteria.java b/sormas-api/src/main/java/de/symeda/sormas/api/outbreak/OutbreakCriteria.java index 7b28c46d9e7..42846d7cfb9 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/outbreak/OutbreakCriteria.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/outbreak/OutbreakCriteria.java @@ -26,6 +26,7 @@ import de.symeda.sormas.api.infrastructure.district.DistrictReferenceDto; import de.symeda.sormas.api.infrastructure.region.RegionReferenceDto; import de.symeda.sormas.api.utils.criteria.BaseCriteria; +import de.symeda.sormas.api.caze.CaseClassification; public class OutbreakCriteria extends BaseCriteria implements Serializable { @@ -39,6 +40,7 @@ public class OutbreakCriteria extends BaseCriteria implements Serializable { private Date changeDateAfter; private Date reportedDateFrom; private Date reportedDateTo; + private CaseClassification caseClassification; public RegionReferenceDto getRegion() { return region; @@ -136,4 +138,15 @@ public OutbreakCriteria reportedDateTo(Date reportedDateTo) { public Date getReportedDateTo() { return reportedDateTo; } + + public CaseClassification getCaseClassification() { + + return caseClassification; + } + + public OutbreakCriteria caseClassification(CaseClassification caseClassification) { + + this.caseClassification = caseClassification; + return this; + } } diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/user/DefaultUserRole.java b/sormas-api/src/main/java/de/symeda/sormas/api/user/DefaultUserRole.java index b91d28b515b..afc901005c1 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/user/DefaultUserRole.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/user/DefaultUserRole.java @@ -311,6 +311,7 @@ public Set getDefaultUserRights() { ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_ARCHIVE, ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_DELETE, ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_EXPORT, + DISEASE_DETAILS_VIEW, PERSON_VIEW, PERSON_EDIT, PERSON_DELETE, @@ -1329,6 +1330,7 @@ public Set getDefaultUserRights() { ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_ARCHIVE, ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_DELETE, ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_EXPORT, + DISEASE_DETAILS_VIEW, PERSON_VIEW, PERSON_EDIT, PERSON_DELETE, diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/user/UserRight.java b/sormas-api/src/main/java/de/symeda/sormas/api/user/UserRight.java index 806a73d1cb6..9208c87739c 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/user/UserRight.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/user/UserRight.java @@ -70,6 +70,7 @@ public enum UserRight { ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_DELETE(UserRightGroup.ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION), ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_EXPORT(UserRightGroup.ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION), + DISEASE_DETAILS_VIEW(UserRightGroup.DISEASE_DETAILS_VIEW), PERSON_VIEW(UserRightGroup.PERSON), PERSON_EDIT(UserRightGroup.PERSON, UserRight._PERSON_VIEW), PERSON_DELETE(UserRightGroup.PERSON, UserRight._PERSON_VIEW, UserRight._VISIT_DELETE), @@ -531,6 +532,7 @@ public enum UserRight { public static final String _EXTERNAL_EMAIL_SEND = "EXTERNAL_EMAIL_SEND"; public static final String _EXTERNAL_EMAIL_ATTACH_DOCUMENTS = "EXTERNAL_EMAIL_ATTACH_DOCUMENTS"; public static final String _CUSTOMIZABLE_ENUM_MANAGEMENT = "CUSTOMIZABLE_ENUM_MANAGEMENT"; + public static final String _DISEASE_DETAILS_VIEW ="DISEASE_DETAILS_VIEW" ; private static final Map> userRightDependencies = buildUserRightDependencies(); diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/user/UserRightGroup.java b/sormas-api/src/main/java/de/symeda/sormas/api/user/UserRightGroup.java index 7fa2dd34069..ada0bee03be 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/user/UserRightGroup.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/user/UserRightGroup.java @@ -48,7 +48,8 @@ public enum UserRightGroup { EXPORT, CONFIGURATION, - EXTERNAL; + EXTERNAL, + DISEASE_DETAILS_VIEW; @Override public String toString() { diff --git a/sormas-api/src/main/resources/captions.properties b/sormas-api/src/main/resources/captions.properties index 1a1c0b040a9..9e129921ab0 100644 --- a/sormas-api/src/main/resources/captions.properties +++ b/sormas-api/src/main/resources/captions.properties @@ -25,6 +25,7 @@ date=Date description=Description disease=Disease districtName=District +dashboardDistrictDiseaseBurden=Dashboard District Diseas eBurden edit=Edit view=View epiWeekFrom=From Epi Week @@ -902,6 +903,8 @@ dashboardData=Data dashboardDead=Dead dashboardDiscarded=Discarded dashboardDiseaseBurdenInfo=Disease Burden Information +dashboardRegionalDiseaseBurden=Regional Disease Burden +diseaseDetailMap=Disease Detail Map dashboardDiseaseBurdenOutbreakDistricts=Outbreak Districts dashboardDiseaseCarouselSlideShow=slide show dashboardDiseaseDifference=Difference in Number of Cases @@ -3104,6 +3107,7 @@ View.dashboard.contacts=Contacts Dashboard View.dashboard.surveillance=Surveillance Dashboard View.dashboard.campaigns=Campaigns Dashboard View.dashboard.samples=Samples Dashboard +View.dashboard.disease=Disease Dashboard View.dashboard.adverseevents=Adverse Events Dashboard View.events=Event Directory View.events.archive=Event Archive @@ -3136,6 +3140,7 @@ View.environments=Environment directory View.selfreports=Self reports directory # Visit visitNewVisit=New visit +viewMore=View More Visit=Visit Visit.person=Visited person Visit.symptoms=Symptoms @@ -3342,6 +3347,7 @@ externalEmailUsedTemplate=Template externalEmailSentBy=Sent by externalEmailSentTo=Sent to externalEmailAttachedDocuments=Attached documents +dashboardLastReportedDistrict=Last reported district # SpecialCaseAccess SpecialCaseAccess=Special case access diff --git a/sormas-api/src/main/resources/enum.properties b/sormas-api/src/main/resources/enum.properties index 3cb4942c2ac..d2c158cb96a 100644 --- a/sormas-api/src/main/resources/enum.properties +++ b/sormas-api/src/main/resources/enum.properties @@ -953,6 +953,12 @@ Month.DECEMBER = December NewCaseDateType.MOST_RELEVANT = Most relevant date NewCaseDateType.ONSET = Symptom onset date NewCaseDateType.REPORT = Case report date +NewDateFilterType.TODAY = Today's date +NewDateFilterType.YESTERDAY = Yesterday's date +NewDateFilterType.THIS_WEEK = Current week's date range +NewDateFilterType.LAST_WEEK = Previous week's date range +NewDateFilterType.THIS_YEAR = Current year's date range +NewDateFilterType.CUSTOM = Custom date range # OccupationType # Temporarily necessary for data migration of older systems; can be removed at a later point in time @@ -1419,6 +1425,7 @@ UserRight.CONTACT_ARCHIVE = Archive contacts UserRight.DASHBOARD_CONTACT_VIEW = Access the contact supervisor dashboard UserRight.DASHBOARD_SURVEILLANCE_VIEW = Access the surveillance supervisor dashboard UserRight.DASHBOARD_SAMPLES_VIEW = Access the samples dashboard +UserRight.DASHBOARD_DISEASE_DETAILS_ACCESS = Access the disease dashboard UserRight.DATABASE_EXPORT_ACCESS = Export the whole database UserRight.EVENT_ARCHIVE = Archive events UserRight.EVENT_CREATE = Create new events @@ -1444,6 +1451,7 @@ UserRight.SAMPLE_DELETE = Delete samples from the system UserRight.SAMPLE_SEE_ARCHIVED = View archived samples UserRight.SAMPLE_TRANSFER = Transfer samples to another lab UserRight.SAMPLE_VIEW = View existing samples +UserRight.DISEASE_DETAILS_ACCESS = Access disease details UserRight.SAMPLETEST_CREATE = Create new sample tests UserRight.SAMPLETEST_EDIT = Edit existing sample tests UserRight.STATISTICS_EXPORT = Export detailed statistics from SORMAS @@ -1557,6 +1565,7 @@ UserRight.ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_EDIT = Edit existing adverse eve UserRight.ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_DELETE = Delete adverse events following immunization from the system UserRight.ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_ARCHIVE = Archive adverse events following immunization UserRight.ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_EXPORT = Export adverse events following immunization +UserRight.DISEASE_DETAILS_VIEW = View details of selected disease UserRight.PERSON_EXPORT = Export persons UserRight.CONTACT_MERGE = Merge contacts UserRight.EVENTGROUP_CREATE = Create new event groups @@ -1672,6 +1681,7 @@ UserRight.Desc.SAMPLE_EXPORT = Able to export samples from SORMAS UserRight.Desc.SAMPLE_DELETE = Able to delete samples from the system UserRight.Desc.SAMPLE_TRANSFER = Able to transfer samples to another lab UserRight.Desc.SAMPLE_VIEW = Able to view existing samples +UserRight.Desc.DISEASE_DETAILS_ACCESS = Able to access disease details UserRight.Desc.SAMPLETEST_CREATE = Able to create new sample tests UserRight.Desc.SAMPLETEST_EDIT = Able to edit existing sample tests UserRight.Desc.STATISTICS_EXPORT = Able to export detailed statistics from SORMAS @@ -1781,6 +1791,7 @@ UserRight.Desc.ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_EDIT = Able to edit existin UserRight.Desc.ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_DELETE = Able to delete adverse events following immunization from the system UserRight.Desc.ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_ARCHIVE = Able to archive adverse events following immunization UserRight.Desc.ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_EXPORT = Able to export adverse events following immunization +UserRight.Desc.DISEASE_DETAILS_VIEW = Able to View details of selected disease UserRight.Desc.PERSON_EXPORT = Able to export persons UserRight.Desc.CONTACT_MERGE = Able to merge contacts UserRight.Desc.EVENTGROUP_CREATE = Able to create new event groups diff --git a/sormas-api/src/main/resources/strings.properties b/sormas-api/src/main/resources/strings.properties index aa9c0c3ea24..003fc625ed6 100644 --- a/sormas-api/src/main/resources/strings.properties +++ b/sormas-api/src/main/resources/strings.properties @@ -99,7 +99,7 @@ classificationCriteriaForTestType = for test type classificationCriteriaForExposureType = for exposure type classificationDaysBeforeCaseStart = days before symptom onset/case report date classificationEventCluster = Case linked to a cluster event -classificationForDisease = for +classificationForDisease = Disease details classificationGeneratedFor = Generated for SORMAS classificationInfoText = ... when the case meets the following requirements:
classificationInfoNumberText = ... when the case meets %s of the following requirements:
diff --git a/sormas-api/src/test/java/de/symeda/sormas/api/outbreak/OutbreakCriteriaTest.java b/sormas-api/src/test/java/de/symeda/sormas/api/outbreak/OutbreakCriteriaTest.java new file mode 100644 index 00000000000..13332b5c0fa --- /dev/null +++ b/sormas-api/src/test/java/de/symeda/sormas/api/outbreak/OutbreakCriteriaTest.java @@ -0,0 +1,115 @@ +package de.symeda.sormas.api.outbreak; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.caze.CaseClassification; +import de.symeda.sormas.api.infrastructure.district.DistrictReferenceDto; +import de.symeda.sormas.api.infrastructure.region.RegionReferenceDto; + +public class OutbreakCriteriaTest { + + @Test + public void testSetAndGetRegion() { + + RegionReferenceDto region = new RegionReferenceDto(); + OutbreakCriteria criteria = new OutbreakCriteria().region(region); + assertEquals(region, criteria.getRegion()); + } + + @Test + public void testSetAndGetDistrict() { + + DistrictReferenceDto district = new DistrictReferenceDto(); + OutbreakCriteria criteria = new OutbreakCriteria().district(district); + assertEquals(district, criteria.getDistrict()); + } + + @Test + public void testSetAndGetDiseases() { + + Set diseases = new HashSet<>(); + diseases.add(Disease.CORONAVIRUS); + OutbreakCriteria criteria = new OutbreakCriteria().diseases(diseases); + assertEquals(diseases, criteria.getDiseases()); + } + + @Test + public void testSetAndGetDisease() { + + Disease disease = Disease.CORONAVIRUS; + OutbreakCriteria criteria = new OutbreakCriteria().disease(disease); + assertEquals(Collections.singleton(disease), criteria.getDiseases()); + } + + @Test + public void testSetAndGetActive() { + + OutbreakCriteria criteria = new OutbreakCriteria().active(true); + assertTrue(criteria.getActive()); + criteria.active(false); + assertFalse(criteria.getActive()); + } + + @Test + public void testSetAndGetActiveWithDates() { + + Date lower = new Date(); + Date upper = new Date(); + OutbreakCriteria criteria = new OutbreakCriteria().active(true, lower, upper); + assertTrue(criteria.getActive()); + assertEquals(lower, criteria.getActiveLower()); + assertEquals(upper, criteria.getActiveUpper()); + } + + @Test + public void testSetAndGetChangeDateAfter() { + + Date changeDate = new Date(); + OutbreakCriteria criteria = new OutbreakCriteria().changeDateAfter(changeDate); + assertEquals(changeDate, criteria.getChangeDateAfter()); + } + + @Test + public void testSetAndGetReportedBetween() { + + Date reportedFrom = new Date(); + Date reportedTo = new Date(); + OutbreakCriteria criteria = new OutbreakCriteria().reportedBetween(reportedFrom, reportedTo); + assertEquals(reportedFrom, criteria.getReportedDateFrom()); + assertEquals(reportedTo, criteria.getReportedDateTo()); + } + + @Test + public void testSetAndGetReportedDateFrom() { + + Date reportedFrom = new Date(); + OutbreakCriteria criteria = new OutbreakCriteria().reportedDateFrom(reportedFrom); + assertEquals(reportedFrom, criteria.getReportedDateFrom()); + } + + @Test + public void testSetAndGetReportedDateTo() { + + Date reportedTo = new Date(); + OutbreakCriteria criteria = new OutbreakCriteria().reportedDateTo(reportedTo); + assertEquals(reportedTo, criteria.getReportedDateTo()); + } + + @Test + public void testSetAndGetCaseClassification() { + + CaseClassification caseClassification = CaseClassification.CONFIRMED; + OutbreakCriteria criteria = new OutbreakCriteria().caseClassification(caseClassification); + assertEquals(caseClassification, criteria.getCaseClassification()); + } +} diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/caze/CaseUserFilterCriteria.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/caze/CaseUserFilterCriteria.java index 0fa3eb01809..f19cf986322 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/caze/CaseUserFilterCriteria.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/caze/CaseUserFilterCriteria.java @@ -8,6 +8,8 @@ public class CaseUserFilterCriteria { private boolean excludeCasesFromContacts; private Boolean includeCasesFromOtherJurisdictions = Boolean.FALSE; private boolean excludeLimitedSyncRestrictions; + private boolean restrictAccessToAssignedEntities; + private boolean excludeSharedCases; public boolean isExcludeCasesFromContacts() { return excludeCasesFromContacts; @@ -39,4 +41,25 @@ public CaseUserFilterCriteria excludeLimitedSyncRestrictions(boolean excludeLimi this.excludeLimitedSyncRestrictions = excludeLimitedSyncRestrictions; return this; } + + public boolean isRestrictAccessToAssignedEntities() { + + return restrictAccessToAssignedEntities; + } + + public void setRestrictAccessToAssignedEntities(boolean restrictAccessToAssignedEntities) { + + this.restrictAccessToAssignedEntities = restrictAccessToAssignedEntities; + } + + public boolean isExcludeSharedCases() { + + return excludeSharedCases; + } + + public CaseUserFilterCriteria excludeSharedCases(boolean excludeSharedCases) { + + this.excludeSharedCases = excludeSharedCases; + return this; + } } diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/DashboardFacadeEjb.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/DashboardFacadeEjb.java index db54beb4ea3..2ebf72ffd27 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/DashboardFacadeEjb.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/DashboardFacadeEjb.java @@ -67,6 +67,11 @@ import de.symeda.sormas.backend.outbreak.OutbreakFacadeEjb; import de.symeda.sormas.backend.sample.SampleFacadeEjb; import de.symeda.sormas.backend.util.RightsAllowed; +import de.symeda.sormas.api.caze.CaseOutcome; +import java.util.Objects; +import de.symeda.sormas.api.infrastructure.region.RegionDto; +import de.symeda.sormas.backend.infrastructure.region.RegionFacadeEjb; +import de.symeda.sormas.backend.person.PersonFacadeEjb; @Stateless(name = "DashboardFacade") public class DashboardFacadeEjb implements DashboardFacade { @@ -95,6 +100,12 @@ public class DashboardFacadeEjb implements DashboardFacade { @EJB private DashboardService dashboardService; + @EJB + private PersonFacadeEjb.PersonFacadeEjbLocal personFacade; + + @EJB + private RegionFacadeEjb.RegionFacadeEjbLocal regionFacade; + @Override @RightsAllowed({ UserRight._DASHBOARD_SURVEILLANCE_VIEW, @@ -795,6 +806,131 @@ public List getDiseaseBurden( return diseasesBurden; } + @Override + @RightsAllowed({UserRight._DISEASE_DETAILS_VIEW}) + public DiseaseBurdenDto getDiseaseForDashboard( + RegionReferenceDto region, + DistrictReferenceDto district, + Disease disease, + Date fromDate, + Date toDate, + Date previousFrom, + Date previousTo, + CriteriaDateType newCaseDateType, + CaseClassification caseClassification) { + + DashboardCriteria dashboardCriteria = + new DashboardCriteria().region(region).district(district).newCaseDateType(newCaseDateType).dateBetween(fromDate, toDate); + + Map newCases = dashboardService.getCaseCountByDisease(dashboardCriteria); + + Map events = eventFacade + .getEventCountByDisease(new EventCriteria().region(region).district(district).eventDateType(null).eventDateBetween(fromDate, toDate)); + + //outbreaks + Map outbreakDistrictsCount; + if (featureConfigurationFacade.isFeatureEnabled(FeatureType.OUTBREAKS)) { + outbreakDistrictsCount = outbreakFacade + .getOutbreakDistrictCountByDisease(new OutbreakCriteria().region(region).district(district).reportedBetween(fromDate, toDate)); + } else { + outbreakDistrictsCount = new HashMap<>(); + } + //outbreaks + Map outbreakDistricts = outbreakFacade + .getOutbreakDistrictNameByDisease(new OutbreakCriteria().disease(disease).region(region).district(district).reportedBetween(fromDate, toDate)); + + //last report district + Map lastReportedDistricts = dashboardService.getLastReportedDistrictByDisease(dashboardCriteria); + + //case fatalities + Map caseFatalities = dashboardService.getDeathCountByDisease(dashboardCriteria); + + //previous cases + dashboardCriteria.dateBetween(previousFrom, previousTo); + Map previousCases = dashboardService.getCaseCountByDisease(dashboardCriteria); + + //build diseasesBurden + Long caseCount = newCases.getOrDefault(disease, 0L); + Long previousCaseCount = previousCases.getOrDefault(disease, 0L); + Long eventCount = events.getOrDefault(disease, 0L); + Long outbreakDistrictCount = outbreakDistrictsCount.getOrDefault(disease, 0L); + Long caseFatalityCount = caseFatalities.getOrDefault(disease, 0L); + District lastReportedDistrict = lastReportedDistricts.getOrDefault(disease, null); + District outbreakDistrict = outbreakDistricts.getOrDefault(disease, null); + + String lastReportedDistrictName = lastReportedDistrict == null ? "" : lastReportedDistrict.getName(); + String outbreakDistrictName = outbreakDistrict == null ? "" : outbreakDistrict.getName(); + + return new DiseaseBurdenDto( + disease, + caseCount, + previousCaseCount, + eventCount, + outbreakDistrictCount, + caseFatalityCount, + lastReportedDistrictName, + outbreakDistrictName, + fromDate, + toDate + ); + } + + @Override + @RightsAllowed({ + UserRight._DISEASE_DETAILS_VIEW}) + public DiseaseBurdenDto getDiseaseGridForDashboard( + RegionReferenceDto region, + DistrictReferenceDto district, + Disease disease, + Date fromDate, + Date toDate, + Date previousFrom, + Date previousTo, + CriteriaDateType newCaseDateType, + CaseClassification caseClassification) { + + //Get the region + RegionDto regionDto = null; + if(Objects.nonNull(region)){ + regionDto = regionFacade.getByUuid(region.getUuid()); + } + + //new cases + DashboardCriteria dashboardCriteria = + new DashboardCriteria().region(region).district(district).newCaseDateType(newCaseDateType).dateBetween(fromDate, toDate); + + //Load count all dead/ fatalities + Map allCasesFetched = dashboardService.getCaseCountByDisease(dashboardCriteria); + + + Map caseFatalities = dashboardService.getDeathCountByDisease(dashboardCriteria); + + dashboardCriteria.setOutcome(CaseOutcome.NO_OUTCOME); + Map archievedCase = dashboardService.getCaseCountByDisease(dashboardCriteria); + + dashboardCriteria.setOutcome(CaseOutcome.RECOVERED); + Map recoveredCase = dashboardService.getCaseCountByDisease(dashboardCriteria); + + dashboardCriteria.setOutcome(CaseOutcome.UNKNOWN); + Map unknown = dashboardService.getCaseCountByDisease(dashboardCriteria); + + //build diseasesBurden + Long totalCaseCount = allCasesFetched.getOrDefault(disease, 0L); + Long activeCaseCount = archievedCase.getOrDefault(disease, 0L); + Long recoveredCaseCount = recoveredCase.getOrDefault(disease, 0L); + Long caseFatalityCount = caseFatalities.getOrDefault(disease, 0L); + Long unknownCaseCount = unknown.getOrDefault(disease, 0L); + + return new DiseaseBurdenDto( + regionDto, + totalCaseCount.toString(), + activeCaseCount.toString(), + recoveredCaseCount.toString(), + caseFatalityCount.toString(), + unknownCaseCount.toString() + ); + } + @LocalBean @Stateless public static class DashboardFacadeEjbLocal extends DashboardFacadeEjb { diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/DashboardService.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/DashboardService.java index 744e0b6988c..ee26ad2e7a4 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/DashboardService.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/dashboard/DashboardService.java @@ -485,6 +485,10 @@ private Predicate createCaseCriteriaFilter( .and(cb, filter, cb.notEqual(caseQueryContext.getRoot().get(Case.CASE_CLASSIFICATION), CaseClassification.NO_CASE)); } + if (dashboardCriteria.getOutcome() != null) { + filter = CriteriaBuilderHelper.and(cb, filter, cb.equal(from.get(Case.OUTCOME), dashboardCriteria.getOutcome())); + } + // Exclude deleted cases. Archived cases should stay included filter = CriteriaBuilderHelper.and(cb, filter, cb.isFalse(from.get(Case.DELETED))); diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/infrastructure/region/RegionFacadeEjb.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/infrastructure/region/RegionFacadeEjb.java index 476ac171b74..e23562958ac 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/infrastructure/region/RegionFacadeEjb.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/infrastructure/region/RegionFacadeEjb.java @@ -68,6 +68,7 @@ import de.symeda.sormas.backend.util.DtoHelper; import de.symeda.sormas.backend.util.QueryHelper; import de.symeda.sormas.backend.util.RightsAllowed; +import de.symeda.sormas.backend.infrastructure.InfrastructureAdo; @Stateless(name = "RegionFacade") @RightsAllowed(UserRight._INFRASTRUCTURE_VIEW) @@ -338,6 +339,34 @@ protected void resetDefaultInfrastructure() { defaultInfrastructureCache.resetDefaultRegion(); } + @Override + public List getAllActiveRegions() { + + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(RegionDto.class); + Root region = cq.from(Region.class); + + Join country = region.join(Region.COUNTRY, JoinType.LEFT); + Join area = region.join(Region.AREA, JoinType.LEFT); + + cq.multiselect( + region.get(AbstractDomainObject.CREATION_DATE), + region.get(AbstractDomainObject.CHANGE_DATE), + region.get(AbstractDomainObject.UUID), + region.get(InfrastructureAdo.ARCHIVED), + region.get(Region.NAME), + region.get(Region.EPID_CODE), + region.get(Region.GROWTH_RATE), + region.get(Region.EXTERNAL_ID), + country.get(AbstractDomainObject.UUID), + country.get(Country.DEFAULT_NAME), + country.get(Country.ISO_CODE), + area.get(AbstractDomainObject.UUID) + ); + + return em.createQuery(cq).getResultList(); + } + @LocalBean @Stateless public static class RegionFacadeEjbLocal extends RegionFacadeEjb { diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/outbreak/OutbreakFacadeEjb.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/outbreak/OutbreakFacadeEjb.java index 93010b391b3..183fb3da0b6 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/outbreak/OutbreakFacadeEjb.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/outbreak/OutbreakFacadeEjb.java @@ -43,6 +43,7 @@ import de.symeda.sormas.backend.user.UserService; import de.symeda.sormas.backend.util.DtoHelper; import de.symeda.sormas.backend.util.RightsAllowed; +import de.symeda.sormas.backend.infrastructure.district.District; @Stateless(name = "OutbreakFacade") @RightsAllowed(UserRight._OUTBREAK_VIEW) @@ -212,6 +213,14 @@ public Long getOutbreakDistrictCount(OutbreakCriteria criteria) { return outbreakService.getOutbreakDistrictCount(criteria, user); } + @RightsAllowed({ + UserRight._DASHBOARD_SURVEILLANCE_VIEW, + UserRight._DASHBOARD_CONTACT_VIEW }) + public Map getOutbreakDistrictNameByDisease(OutbreakCriteria criteria) { + + return outbreakService.getOutbreakDistrictNameByDisease(criteria); + } + @LocalBean @Stateless public static class OutbreakFacadeEjbLocal extends OutbreakFacadeEjb { diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/outbreak/OutbreakService.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/outbreak/OutbreakService.java index b5575e5402e..2e5ce59dc69 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/outbreak/OutbreakService.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/outbreak/OutbreakService.java @@ -47,6 +47,8 @@ import de.symeda.sormas.backend.infrastructure.region.Region; import de.symeda.sormas.backend.user.User; import de.symeda.sormas.backend.util.QueryHelper; +import java.util.HashMap; +import de.symeda.sormas.backend.caze.Case; @Stateless @LocalBean @@ -238,4 +240,73 @@ public Long getOutbreakDistrictCount(OutbreakCriteria criteria, User user) { return em.createQuery(cq).getResultList().stream().findFirst().orElse(0L); } + + public Map getOutbreakDistrictNameByDisease(OutbreakCriteria criteria) { + + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(Object[].class); + Root outbreak = cq.from(Outbreak.class); + Join districtJoin = outbreak.join(Case.DISTRICT, JoinType.LEFT); + + Predicate filter = this.buildCriteriaFilter(criteria, cb, outbreak); + filter = CriteriaBuilderHelper.and(cb, filter, createUserFilter(cb, cq, outbreak)); + + if (filter != null) + cq.where(filter); + + + Expression maxReportDate = cb.max(outbreak.get(Outbreak.REPORT_DATE)); + cq.multiselect(outbreak.get(Outbreak.DISEASE), districtJoin, maxReportDate); + cq.groupBy(outbreak.get(Outbreak.DISEASE), districtJoin); + cq.orderBy(cb.desc(maxReportDate)); + + List results = em.createQuery(cq).getResultList(); + + Map outbreaksDistrict = new HashMap<>(); + for (Object[] e : results) { + Disease disease = (Disease) e[0]; + outbreaksDistrict.computeIfAbsent(disease, k -> (District) e[1]); + } + + return outbreaksDistrict; + } + + public Map getOutbreakDistrictCountByDisease(OutbreakCriteria criteria) { + + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(Object[].class); + Root outbreak = cq.from(Outbreak.class); + cq.multiselect(outbreak.get(Outbreak.DISEASE), cb.countDistinct(outbreak.get(Outbreak.DISTRICT))); + cq.groupBy(outbreak.get(Outbreak.DISEASE)); + + Predicate filter = this.buildCriteriaFilter(criteria, cb, outbreak); + filter = CriteriaBuilderHelper.and(cb, filter, createUserFilter(cb, cq, outbreak)); + + if (filter != null) + cq.where(filter); + + List results = em.createQuery(cq).getResultList(); + + return results.stream().collect(Collectors.toMap(e -> (Disease) e[0], e -> (Long) e[1])); + } + + public Long getOutbreakDistrictCount(OutbreakCriteria criteria) { + + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(Long.class); + Root outbreak = cq.from(getElementClass()); + + Join regionJoin = outbreak.join(Outbreak.DISTRICT, JoinType.LEFT); + cq.groupBy(regionJoin); + + Predicate filter = this.buildCriteriaFilter(criteria, cb, outbreak); + filter = CriteriaBuilderHelper.and(cb, filter, createUserFilter(cb, cq, outbreak)); + + if (filter != null) + cq.where(filter); + + cq.select(cb.count(outbreak)); + + return em.createQuery(cq).getResultList().stream().findFirst().orElse(0L); + } } diff --git a/sormas-backend/src/main/resources/META-INF/glassfish-ejb-jar.xml b/sormas-backend/src/main/resources/META-INF/glassfish-ejb-jar.xml index c907e3669f4..79a1412f56a 100644 --- a/sormas-backend/src/main/resources/META-INF/glassfish-ejb-jar.xml +++ b/sormas-backend/src/main/resources/META-INF/glassfish-ejb-jar.xml @@ -642,6 +642,11 @@ DASHBOARD_ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_VIEW + + DISEASE_DETAILS_VIEW + DISEASE_DETAILS_VIEW + + CASE_CLINICIAN_VIEW CASE_CLINICIAN_VIEW diff --git a/sormas-backend/src/main/resources/sql/sormas_schema.sql b/sormas-backend/src/main/resources/sql/sormas_schema.sql index 5b55069ae30..cdfcba874fe 100644 --- a/sormas-backend/src/main/resources/sql/sormas_schema.sql +++ b/sormas-backend/src/main/resources/sql/sormas_schema.sql @@ -13669,7 +13669,6 @@ INSERT INTO userroles_userrights (userrole_id, userright) SELECT id, 'ADVERSE_EV INSERT INTO schema_version (version_number, comment) VALUES (552, 'Adverse Events Following Immunization (AEFI) - Entities #12634'); - -- 2024-10-23 Add "Disease" Attribute to Document Templates for Filtering #13160 CREATE TABLE documenttemplates ( id bigint not null, @@ -13698,4 +13697,9 @@ CREATE TRIGGER delete_history_trigger ALTER TABLE documenttemplates_history OWNER TO sormas_user; INSERT INTO schema_version (version_number, comment, upgradeneeded) VALUES (553, 'Add "Disease" Attribute to Document Templates for Filtering #13160', true); + +-- Assign DISEASE_DETAILS_VIEW user rights to default admin and national_user user roles +INSERT INTO userroles_userrights (userrole_id, userright) SELECT id, 'DISEASE_DETAILS_VIEW' FROM public.userroles WHERE userroles.linkeddefaultuserrole in ('ADMIN','NATIONAL_USER'); + +INSERT INTO schema_version (version_number, comment) VALUES (554, 'Dashboard Diseases Details View - #12880'); -- *** Insert new sql commands BEFORE this line. Remember to always consider _history tables. *** diff --git a/sormas-backend/src/test/java/de/symeda/sormas/backend/AbstractBeanTest.java b/sormas-backend/src/test/java/de/symeda/sormas/backend/AbstractBeanTest.java index 4a940ef86a5..17437e32085 100644 --- a/sormas-backend/src/test/java/de/symeda/sormas/backend/AbstractBeanTest.java +++ b/sormas-backend/src/test/java/de/symeda/sormas/backend/AbstractBeanTest.java @@ -275,6 +275,7 @@ import de.symeda.sormas.backend.vaccination.VaccinationService; import de.symeda.sormas.backend.visit.VisitFacadeEjb.VisitFacadeEjbLocal; import de.symeda.sormas.backend.visit.VisitService; +import de.symeda.sormas.backend.outbreak.OutbreakService; @ExtendWith(CdiTestJunitExtension.class) @ExtendWith(MockitoExtension.class) @@ -657,6 +658,11 @@ public OutbreakFacade getOutbreakFacade() { return getBean(OutbreakFacadeEjbLocal.class); } + public OutbreakService getOutbreakService() { + + return getBean(OutbreakService.class); + } + public ImportFacade getImportFacade() { return getBean(ImportFacadeEjbLocal.class); } diff --git a/sormas-backend/src/test/java/de/symeda/sormas/backend/caze/CaseUserFilterCriteriaTest.java b/sormas-backend/src/test/java/de/symeda/sormas/backend/caze/CaseUserFilterCriteriaTest.java new file mode 100644 index 00000000000..43e189934b9 --- /dev/null +++ b/sormas-backend/src/test/java/de/symeda/sormas/backend/caze/CaseUserFilterCriteriaTest.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * 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.backend.caze; + +import de.symeda.sormas.backend.AbstractBeanTest; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class CaseUserFilterCriteriaTest extends AbstractBeanTest { + @Test + public void testRestrictAccessToAssignedEntities() { + + CaseUserFilterCriteria criteria = new CaseUserFilterCriteria(); + + // Test default value + assertFalse(criteria.isRestrictAccessToAssignedEntities()); + + // Set and test new value + criteria.setRestrictAccessToAssignedEntities(true); + assertTrue(criteria.isRestrictAccessToAssignedEntities()); + + // Set and test another value + criteria.setRestrictAccessToAssignedEntities(false); + assertFalse(criteria.isRestrictAccessToAssignedEntities()); + } + + @Test + public void testExcludeSharedCases() { + + CaseUserFilterCriteria criteria = new CaseUserFilterCriteria(); + + // Test default value + assertFalse(criteria.isExcludeSharedCases()); + + // Set and test new value using the setter method + criteria.excludeSharedCases(true); + assertTrue(criteria.isExcludeSharedCases()); + + // Set and test another value using the setter method + criteria.excludeSharedCases(false); + assertFalse(criteria.isExcludeSharedCases()); + } +} \ No newline at end of file diff --git a/sormas-backend/src/test/java/de/symeda/sormas/backend/dashboard/DashboardFacadeEjbTest.java b/sormas-backend/src/test/java/de/symeda/sormas/backend/dashboard/DashboardFacadeEjbTest.java index 123065a489f..3e5d099a243 100644 --- a/sormas-backend/src/test/java/de/symeda/sormas/backend/dashboard/DashboardFacadeEjbTest.java +++ b/sormas-backend/src/test/java/de/symeda/sormas/backend/dashboard/DashboardFacadeEjbTest.java @@ -37,6 +37,10 @@ import de.symeda.sormas.api.utils.DateHelper; import de.symeda.sormas.backend.AbstractBeanTest; import de.symeda.sormas.backend.TestDataCreator.RDCF; +import de.symeda.sormas.api.dashboard.DashboardCaseMeasureDto; +import de.symeda.sormas.api.dashboard.EpiCurveGrouping; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertNotNull; public class DashboardFacadeEjbTest extends AbstractBeanTest { @@ -309,4 +313,154 @@ private void createCasesForPersonWithCondition(PresentCondition presentCondition rdcf); } } + + @Test + public void testGetCasesCountByClassification() { + + // Create necessary data for testing + RDCF rdcf = creator.createRDCF(); + UserDto user = creator.createSurveillanceSupervisor(rdcf); + Date currentDate = new Date(); + + // Create cases with different classifications + creator.createCase( + user.toReference(), + creator.createPerson("Case", "Person1").toReference(), + Disease.EVD, + CaseClassification.CONFIRMED, + InvestigationStatus.PENDING, + currentDate, + rdcf); + + creator.createCase( + user.toReference(), + creator.createPerson("Case", "Person2").toReference(), + Disease.EVD, + CaseClassification.PROBABLE, + InvestigationStatus.PENDING, + currentDate, + rdcf); + + creator.createCase( + user.toReference(), + creator.createPerson("Case", "Person3").toReference(), + Disease.EVD, + CaseClassification.SUSPECT, + InvestigationStatus.PENDING, + currentDate, + rdcf); + + // Define dashboard criteria + DashboardCriteria dashboardCriteria = new DashboardCriteria() + .region(rdcf.region) + .district(rdcf.district) + .disease(Disease.EVD) + .newCaseDateType(NewCaseDateType.MOST_RELEVANT) + .dateBetween(DateHelper.subtractDays(currentDate, 1), DateHelper.addDays(currentDate, 1)); + + // Get counts by classification + Map casesCountByClassification = getDashboardFacade().getCasesCountByClassification(dashboardCriteria); + + // Verify the results + assertEquals(1, casesCountByClassification.get(CaseClassification.CONFIRMED)); + assertEquals(1, casesCountByClassification.get(CaseClassification.PROBABLE)); + assertEquals(1, casesCountByClassification.get(CaseClassification.SUSPECT)); + } + + @Test + public void testGetCaseMeasurePerDistrict() { + + // Create necessary data for testing + RDCF rdcf = creator.createRDCF(); + Date currentDate = new Date(); + + // Define dashboard criteria + DashboardCriteria dashboardCriteria = new DashboardCriteria() + .region(rdcf.region) + .district(rdcf.district) + .disease(Disease.EVD) + .newCaseDateType(NewCaseDateType.MOST_RELEVANT) + .dateBetween(DateHelper.subtractDays(currentDate, 1), DateHelper.addDays(currentDate, 1)); + + // Call the method under test + DashboardCaseMeasureDto result = getDashboardFacade().getCaseMeasurePerDistrict(dashboardCriteria); + + // Assertions + assertNotNull(result); // Ensure the result is not null + } + + @Test + public void testCountCasesConvertedFromContacts() { + + // Create necessary data for testing + RDCF rdcf = creator.createRDCF(); + Date currentDate = new Date(); + + // Define dashboard criteria + DashboardCriteria dashboardCriteria = new DashboardCriteria() + .region(rdcf.region) + .district(rdcf.district) + .disease(Disease.EVD) + .newCaseDateType(NewCaseDateType.MOST_RELEVANT) + .dateBetween(DateHelper.subtractDays(currentDate, 1), DateHelper.addDays(currentDate, 1)); + + // Call the method under test + long result = getDashboardFacade().countCasesConvertedFromContacts(dashboardCriteria); + + // Assertions + assertEquals(0, result); // Assuming no cases converted from contacts + } + + @Test + public void testGetEventCountByStatus() { + + // Create necessary data for testing + RDCF rdcf = creator.createRDCF(); + UserDto user = creator.createSurveillanceSupervisor(rdcf); + Date currentDate = new Date(); + + // Create mock events with different statuses + creator.createEvent(user.toReference(), Disease.EVD, rdcf); + creator.createEvent(user.toReference(), Disease.MALARIA, rdcf); + creator.createEvent(user.toReference(), Disease.NEW_INFLUENZA, rdcf); + + // Define dashboard criteria + DashboardCriteria dashboardCriteria = new DashboardCriteria() + .region(rdcf.region) + .district(rdcf.district) + .disease(Disease.EVD) // Adjust disease as needed for your test case + .newCaseDateType(NewCaseDateType.MOST_RELEVANT) + .dateBetween(DateHelper.subtractDays(currentDate, 1), DateHelper.addDays(currentDate, 1)); + + // Call the method under test + Map result = getDashboardFacade().getEventCountByStatus(dashboardCriteria); + + // Assertions + assertNotNull(result); // Ensure result is not null + + // Verify specific status counts (adjust these based on your mock events and expected logic) + assertTrue(result.containsKey(EventStatus.SIGNAL)); + assertEquals(1L, result.getOrDefault(EventStatus.SIGNAL, 0L).longValue()); // Example assertion + } + + @Test + public void testGetIntervalEndDate() { + + DashboardFacadeEjb dashboardFacadeEjb = new DashboardFacadeEjb(); + + // Test case 1: DAY grouping + Date startDate1 = new Date(); // Replace with actual date values + Date expectedEndDate1 = DateHelper.getEndOfDay(startDate1); + assertEquals(expectedEndDate1, dashboardFacadeEjb.getIntervalEndDate(startDate1, EpiCurveGrouping.DAY)); + + // Test case 2: WEEK grouping + Date startDate2 = new Date(); // Replace with actual date values + Date expectedEndDate2 = DateHelper.getEndOfWeek(startDate2); + assertEquals(expectedEndDate2, dashboardFacadeEjb.getIntervalEndDate(startDate2, EpiCurveGrouping.WEEK)); + + // Test case 3: MONTH grouping + Date startDate3 = new Date(); // Replace with actual date values + Date expectedEndDate3 = DateHelper.getEndOfMonth(startDate3); + assertEquals(expectedEndDate3, dashboardFacadeEjb.getIntervalEndDate(startDate3, EpiCurveGrouping.MONTH)); + } } diff --git a/sormas-backend/src/test/java/de/symeda/sormas/backend/disease/DiseaseFacadeEjbTest.java b/sormas-backend/src/test/java/de/symeda/sormas/backend/disease/DiseaseFacadeEjbTest.java new file mode 100644 index 00000000000..a44d41c58f8 --- /dev/null +++ b/sormas-backend/src/test/java/de/symeda/sormas/backend/disease/DiseaseFacadeEjbTest.java @@ -0,0 +1,126 @@ +/******************************************************************************* + * 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.backend.disease; + +import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.caze.CaseClassification; +import de.symeda.sormas.api.caze.NewCaseDateType; +import de.symeda.sormas.api.disease.DiseaseBurdenDto; +import de.symeda.sormas.api.infrastructure.district.DistrictReferenceDto; +import de.symeda.sormas.api.infrastructure.region.RegionReferenceDto; +import de.symeda.sormas.api.utils.criteria.CriteriaDateType; +import de.symeda.sormas.backend.AbstractBeanTest; +import de.symeda.sormas.backend.TestDataCreator; +import org.junit.jupiter.api.Test; + +import java.util.Date; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class DiseaseFacadeEjbTest extends AbstractBeanTest { + + private TestDataCreator.RDCF rdcf; + + @Override + public void init() { + + super.init(); + + rdcf = creator.createRDCF(); + loginWith(creator.createSurveillanceSupervisor(rdcf)); + } + + @Test + public void testGetDiseaseForDashboard() { + + TestDataCreator.RDCF rdcf2 = creator.createRDCF("Region2", "District2", "Community2", "Facility2"); + + RegionReferenceDto region = new RegionReferenceDto(rdcf2.region.getUuid()); + + DistrictReferenceDto district = new DistrictReferenceDto(rdcf.district.getUuid(), null, null); + Disease disease = Disease.EVD; + Date fromDate = new Date(); + Date toDate = new Date(); + Date previousFrom = new Date(fromDate.getTime() - 1000L * 60 * 60 * 24 * 7); // 1 week before fromDate + Date previousTo = new Date(toDate.getTime() - 1000L * 60 * 60 * 24 * 7); // 1 week before toDate + CriteriaDateType newCaseDateType = NewCaseDateType.MOST_RELEVANT; + CaseClassification caseClassification = CaseClassification.CONFIRMED; + + DiseaseBurdenDto result = getDashboardFacade().getDiseaseForDashboard( + region, + district, + disease, + fromDate, + toDate, + previousFrom, + previousTo, + newCaseDateType, + caseClassification + ); + + assertNotNull(result); + assertEquals(disease, result.getDisease()); + assertEquals(0L, result.getCaseCount().longValue()); + assertEquals(0L, result.getPreviousCaseCount().longValue()); + assertEquals(0L, result.getEventCount().longValue()); + assertEquals(0L, result.getOutbreakDistrictCount().longValue()); + assertEquals(0L, result.getCaseDeathCount()); + assertEquals("", result.getLastReportedDistrictName()); + assertEquals("", result.getOutbreakDistrict()); + assertEquals(fromDate, result.getFromDate()); + assertEquals(toDate, result.getToDate()); + } + + @Test + public void testGetDiseaseGridForDashboard() { + + TestDataCreator.RDCF rdcf2 = creator.createRDCF("Region2", "District2", "Community2", "Facility2"); + + RegionReferenceDto region = new RegionReferenceDto(rdcf2.region.getUuid()); + + DistrictReferenceDto district = new DistrictReferenceDto(rdcf.district.getUuid(), null, null); + Disease disease = Disease.EVD; + Date fromDate = new Date(); + Date toDate = new Date(); + Date previousFrom = new Date(fromDate.getTime() - 1000L * 60 * 60 * 24 * 7); // 1 week before fromDate + Date previousTo = new Date(toDate.getTime() - 1000L * 60 * 60 * 24 * 7); // 1 week before toDate + CriteriaDateType newCaseDateType = NewCaseDateType.MOST_RELEVANT; + CaseClassification caseClassification = CaseClassification.CONFIRMED; + + DiseaseBurdenDto result = getDashboardFacade().getDiseaseGridForDashboard( + region, + district, + disease, + fromDate, + toDate, + previousFrom, + previousTo, + newCaseDateType, + caseClassification + ); + + assertNotNull(result); + assertEquals(region.getUuid(), result.getRegion().getUuid()); + assertEquals("0", result.getTotal()); + assertEquals("0", result.getActiveCases()); + assertEquals("0", result.getRecovered()); + assertEquals("0", result.getDeaths()); + assertEquals("0", result.getOther()); + } +} \ No newline at end of file diff --git a/sormas-backend/src/test/java/de/symeda/sormas/backend/geo/RegionFacadeEjbTest.java b/sormas-backend/src/test/java/de/symeda/sormas/backend/geo/RegionFacadeEjbTest.java index f4c5714a77c..09c6ab97b4c 100644 --- a/sormas-backend/src/test/java/de/symeda/sormas/backend/geo/RegionFacadeEjbTest.java +++ b/sormas-backend/src/test/java/de/symeda/sormas/backend/geo/RegionFacadeEjbTest.java @@ -10,6 +10,11 @@ import de.symeda.sormas.api.infrastructure.region.RegionDto; import de.symeda.sormas.backend.AbstractBeanTest; import de.symeda.sormas.backend.infrastructure.region.Region; +import static org.junit.jupiter.api.Assertions.assertThrows; +import de.symeda.sormas.api.infrastructure.region.RegionCriteria; +import de.symeda.sormas.api.infrastructure.region.RegionIndexDto; +import de.symeda.sormas.api.utils.SortProperty; +import java.util.ArrayList; public class RegionFacadeEjbTest extends AbstractBeanTest { @@ -42,4 +47,99 @@ public void testGetAllActiveAsReference() { assertEquals(1, getRegionFacade().getAllActiveAsReference().size()); } + + @Test + public void testGetAllRegion() { + + // Arrange + creator.createRegion("region1"); + creator.createRegion("region2"); + getRegionService().doFlush(); + + // Act + List results = getRegionFacade().getAllActiveRegions(); + + // Assert + assertEquals(2, results.size()); + + RegionDto result1 = results.stream().filter(r -> r.getName().equals("region1")).findFirst().orElse(null); + RegionDto result2 = results.stream().filter(r -> r.getName().equals("region2")).findFirst().orElse(null); + + assertEquals("region1", result1.getName()); + assertEquals("region2", result2.getName()); + } + + @Test + public void testGetByName() { + + // Arrange + creator.createRegion("region1"); + getRegionService().doFlush(); + + // Act + List results = getRegionFacade().getByName("region1", true); + + // Assert + assertEquals(1, results.size()); + + RegionDto result1 = results.stream().filter(r -> r.getName().equals("region1")).findFirst().orElse(null); + + assertEquals("region1", result1.getName()); + } + + @Test + public void testGetIndexListNoCriteria() { + + // Arrange + creator.createRegion("region1"); + creator.createRegion("region2"); + getRegionService().doFlush(); + + // Act + List results = getRegionFacade().getIndexList(null, 0, 10, null); + + // Assert + assertEquals(2, results.size()); + } + + @Test + public void testGetIndexList_withCriteriaAndSortProperties() { + + // Arrange + creator.createRegion("region1"); + creator.createRegion("region2"); + creator.createRegion("region3"); + getRegionService().doFlush(); + + RegionCriteria criteria = new RegionCriteria(); + List sortProperties = new ArrayList<>(); + sortProperties.add(new SortProperty("name", true)); + + // Act + List results = getRegionFacade().getIndexList(criteria, 0, 10, sortProperties); + + // Assert + assertEquals(3, results.size()); + + RegionIndexDto result1 = results.stream().filter(r -> r.getName().equals("region1")).findFirst().orElse(null); + RegionIndexDto result2 = results.stream().filter(r -> r.getName().equals("region2")).findFirst().orElse(null); + RegionIndexDto result3 = results.stream().filter(r -> r.getName().equals("region3")).findFirst().orElse(null); + + assertEquals("region1", result1.getName()); + assertEquals("region2", result2.getName()); + assertEquals("region3", result3.getName()); + } + + @Test + public void testGetIndexList_withInvalidSortProperty() { + + // Arrange + List sortProperties = new ArrayList<>(); + sortProperties.add(new SortProperty("invalidProperty", true)); + + // Act & Assert + assertThrows(IllegalArgumentException.class, () -> { + getRegionFacade().getIndexList(null, 0, 10, sortProperties); + }); + } } diff --git a/sormas-backend/src/test/java/de/symeda/sormas/backend/outbreak/OutbreakFacadeEjbTest.java b/sormas-backend/src/test/java/de/symeda/sormas/backend/outbreak/OutbreakFacadeEjbTest.java index 1d7f3ec9664..2875c1c3575 100644 --- a/sormas-backend/src/test/java/de/symeda/sormas/backend/outbreak/OutbreakFacadeEjbTest.java +++ b/sormas-backend/src/test/java/de/symeda/sormas/backend/outbreak/OutbreakFacadeEjbTest.java @@ -34,6 +34,10 @@ import de.symeda.sormas.api.outbreak.OutbreakDto; import de.symeda.sormas.backend.AbstractBeanTest; import de.symeda.sormas.backend.TestDataCreator.RDCF; +import de.symeda.sormas.backend.infrastructure.district.District; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; public class OutbreakFacadeEjbTest extends AbstractBeanTest { @@ -103,4 +107,57 @@ public void testGetActiveOutbreaksWhenOneHasCaseSurveillanceEnabledFalse() { assertFalse(outbreakDiseases.contains(Disease.AFP)); assertTrue(outbreakDiseases.contains(Disease.CHOLERA)); } + + @Test + public void testGetOutbreakDistrictNameByDisease() { + + Disease disease1 = Disease.EVD; + Disease disease2 = Disease.ADENOVIRUS; + Disease disease3 = Disease.C_PNEUMONIAE; + + DistrictReferenceDto district = new DistrictReferenceDto(rdcf.district.getUuid(), null, null); + getOutbreakFacade().startOutbreak(district, disease1); + getOutbreakFacade().startOutbreak(district, disease2); + getOutbreakFacade().startOutbreak(district, disease3); + + OutbreakCriteria outbreakCriteria = new OutbreakCriteria().district(district); + + Map result =getOutbreakService().getOutbreakDistrictNameByDisease(outbreakCriteria); + + assertEquals(3, result.size()); + Set resultDiseases = result.keySet(); + assertTrue(resultDiseases.contains(disease1)); + assertTrue(resultDiseases.contains(disease2)); + } + + @Test + public void testGetOutbreakDistrictCountByDisease() { + + Disease disease1 = Disease.ADENOVIRUS; + Disease disease2 = Disease.ANTHRAX; + + DistrictReferenceDto district = new DistrictReferenceDto(rdcf.district.getUuid(), null, null); + getOutbreakFacade().startOutbreak(district, disease1); + getOutbreakFacade().startOutbreak(district, disease2); + + Set diseases = new HashSet<>(); + OutbreakCriteria outbreakCriteria = new OutbreakCriteria().diseases(diseases); + + Map result = getOutbreakFacade().getOutbreakDistrictCountByDisease(outbreakCriteria); + + assertEquals(2, result.size()); + } + + @Test + public void testGetOutbreakDistrictCount() { + + Disease disease1 = Disease.ADENOVIRUS; + + DistrictReferenceDto district = new DistrictReferenceDto(rdcf.district.getUuid(), null, null); + getOutbreakFacade().startOutbreak(district, disease1); + OutbreakCriteria outbreakCriteria= new OutbreakCriteria().district(district); + Long result = getOutbreakFacade().getOutbreakDistrictCount(outbreakCriteria); + + assertEquals(1, result); + } } diff --git a/sormas-rest/src/main/webapp/WEB-INF/glassfish-web.xml b/sormas-rest/src/main/webapp/WEB-INF/glassfish-web.xml index d9f6a362c55..12fc5998e12 100644 --- a/sormas-rest/src/main/webapp/WEB-INF/glassfish-web.xml +++ b/sormas-rest/src/main/webapp/WEB-INF/glassfish-web.xml @@ -625,6 +625,11 @@ DASHBOARD_ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_VIEW + + DISEASE_DETAILS_VIEW + DISEASE_DETAILS_VIEW + + CASE_CLINICIAN_VIEW CASE_CLINICIAN_VIEW diff --git a/sormas-rest/src/main/webapp/WEB-INF/web.xml b/sormas-rest/src/main/webapp/WEB-INF/web.xml index dc2a67e6b83..b5a41db8405 100644 --- a/sormas-rest/src/main/webapp/WEB-INF/web.xml +++ b/sormas-rest/src/main/webapp/WEB-INF/web.xml @@ -508,6 +508,10 @@ DASHBOARD_ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_VIEW + + DISEASE_DETAILS_VIEW + + CASE_CLINICIAN_VIEW diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/AbstractDashboardDataProvider.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/AbstractDashboardDataProvider.java index c7da83410b6..3004155db49 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/AbstractDashboardDataProvider.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/AbstractDashboardDataProvider.java @@ -21,6 +21,12 @@ import de.symeda.sormas.api.dashboard.BaseDashboardCriteria; import de.symeda.sormas.api.infrastructure.district.DistrictReferenceDto; import de.symeda.sormas.api.infrastructure.region.RegionReferenceDto; +import de.symeda.sormas.api.utils.DateHelper; +import de.symeda.sormas.api.utils.criteria.CriteriaDateType; +import de.symeda.sormas.api.caze.CaseClassification; +import de.symeda.sormas.api.caze.NewCaseDateType; +import de.symeda.sormas.api.dashboard.DashboardCriteria; +import de.symeda.sormas.api.dashboard.NewDateFilterType; public abstract class AbstractDashboardDataProvider> { @@ -31,6 +37,12 @@ public abstract class AbstractDashboardDataProvider { @@ -71,6 +72,9 @@ public class DashboardDataProvider extends AbstractDashboardDataProvider diseasesBurden) { this.diseasesBurden = diseasesBurden; } + public DiseaseBurdenDto getDiseaseBurdenDetail() { + + return diseaseBurdenDetail; + } + + public void setDiseaseBurdenDetail(DiseaseBurdenDto diseaseBurdenDetail) { + + this.diseaseBurdenDetail = diseaseBurdenDetail; + } + public Long getOutbreakDistrictCount() { return outbreakDistrictCount; } @@ -377,4 +406,19 @@ public Long getCaseWithReferenceDefinitionFulfilledCount() { public void setCaseWithReferenceDefinitionFulfilledCount(Long caseWithReferenceDefinitionFulfilledCount) { this.caseWithReferenceDefinitionFulfilledCount = caseWithReferenceDefinitionFulfilledCount; } + + public CaseClassification getCaseClassification() { + + return caseClassification; + } + + public void setCaseClassification(CaseClassification caseClassification) { + + this.caseClassification = caseClassification; + } + + public void setDateFilterType(NewDateFilterType dateFilterType) { + + this.dateFilterType = dateFilterType; + } } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/DashboardType.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/DashboardType.java index d1325b7ccb7..b8cfd1598d8 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/DashboardType.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/DashboardType.java @@ -25,7 +25,8 @@ public enum DashboardType { CONTACTS, CAMPAIGNS, SAMPLES, - ADVERSE_EVENTS; + ADVERSE_EVENTS, + DISEASE; @Override public String toString() { diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/components/DashboardFilterLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/components/DashboardFilterLayout.java index 1af43734137..48863e81294 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/components/DashboardFilterLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/components/DashboardFilterLayout.java @@ -62,14 +62,21 @@ import de.symeda.sormas.ui.utils.CssStyles; import de.symeda.sormas.ui.utils.DateFormatHelper; import de.symeda.sormas.ui.utils.EpiWeekAndDateFilterComponent; +import com.vaadin.navigator.ViewChangeListener; +import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.dashboard.DashboardCriteria; +import de.symeda.sormas.api.dashboard.NewDateFilterType; +import de.symeda.sormas.ui.dashboard.DashboardType; +import java.util.function.Consumer; @SuppressWarnings("serial") -public abstract class DashboardFilterLayout

extends HorizontalLayout { +public class DashboardFilterLayout

extends HorizontalLayout { public static final String DATE_FILTER = "dateFilter"; public static final String REGION_FILTER = "regionFilter"; public static final String DISTRICT_FILTER = "districtFilter"; private static final String RESET_AND_APPLY_BUTTONS = "resetAndApplyButtons"; + public static final String CASE_CLASSIFICATION_FILTER = "caseClassificationFilter"; protected AbstractDashboardView dashboardView; protected P dashboardDataProvider; @@ -101,6 +108,10 @@ public abstract class DashboardFilterLayout

diseaseFilterChangeCallback; + private Label infoLabel; + private ComboBox diseaseFilter; + private ComboBox caseClassificationFilter; public DashboardFilterLayout(AbstractDashboardView dashboardView, P dashboardDataProvider, String[] templateContent) { this.dashboardView = dashboardView; @@ -124,6 +135,19 @@ public DashboardFilterLayout(AbstractDashboardView dashboardView, P dashboardDat addComponent(customLayout); populateLayout(); + + if(currentDateFilterType!=null) { + String dateFilterType = currentDateFilterType.name(); + dashboardDataProvider.setDateFilterType(NewDateFilterType.valueOf(dateFilterType)); + } + + if (dashboardDataProvider.getDashboardType() == DashboardType.SURVEILLANCE) { + createRegionAndDistrictFilter(); + } + if (dashboardDataProvider.getDashboardType() == DashboardType.CONTACTS) { + createRegionAndDistrictFilter(); + createDiseaseFilter(); + } } public void populateLayout() { @@ -131,6 +155,26 @@ public void populateLayout() { createResetAndApplyButtons(); }; + private void createDiseaseFilter() { + + diseaseFilter.setWidth(200, Unit.PIXELS); + diseaseFilter.setInputPrompt(I18nProperties.getString(Strings.promptDisease)); + if (dashboardDataProvider.getDashboardType() == DashboardType.CONTACTS) { + diseaseFilter.addItems(FacadeProvider.getDiseaseConfigurationFacade().getAllDiseasesWithFollowUp(true, true, true).toArray()); + diseaseFilter.setValue(dashboardDataProvider.getDisease()); + } else { + diseaseFilter.addItems(FacadeProvider.getDiseaseConfigurationFacade().getAllDiseases(true, true, true).toArray()); + } + diseaseFilter.addValueChangeListener(e -> { + if (diseaseFilterChangeCallback != null) { + diseaseFilterChangeCallback.accept(diseaseFilter.getValue() != null); + } + dashboardDataProvider.setDisease((Disease) diseaseFilter.getValue()); + dashboardView.refreshDashboard(); + }); + addComponent(diseaseFilter); + } + protected void createRegionAndDistrictFilter() { createRegionFilter(null); createDistrictFilter(null); @@ -218,7 +262,7 @@ public void createDateFilters() { activeComparisonButton = btnPeriodBefore; currentDateFilterType = DateFilterType.THIS_WEEK; setDateFilter(DateHelper.getStartOfWeek(new Date()), new Date()); - updateComparisonButtons(DateFilterType.THIS_WEEK, DateHelper.getStartOfWeek(new Date()), new Date(), false); + updateComparisonButtons(NewDateFilterType.THIS_WEEK, DateHelper.getStartOfWeek(new Date()), new Date(), false); btnCurrentPeriod.setCaption(btnThisWeek.getCaption()); } @@ -241,7 +285,11 @@ private HorizontalLayout createDateFilterButtonsLayout() { Date to = now; setDateFilter(from, to); btnCurrentPeriod.setCaption(btnToday.getCaption()); - updateComparisonButtons(DateFilterType.TODAY, from, to, false); + updateComparisonButtons(NewDateFilterType.TODAY, from, to, false); + if (DashboardType.DISEASE.equals(dashboardDataProvider.getDashboardType())) + dashboardView.refreshDiseaseData(); + else + dashboardView.refreshDashboard(); }); btnYesterday = createAndAddDateFilterButton( @@ -256,7 +304,11 @@ private HorizontalLayout createDateFilterButtonsLayout() { Date to = DateHelper.getEndOfDay(DateHelper.subtractDays(now, 1)); setDateFilter(from, to); btnCurrentPeriod.setCaption(btnYesterday.getCaption()); - updateComparisonButtons(DateFilterType.YESTERDAY, from, to, false); + updateComparisonButtons(NewDateFilterType.YESTERDAY, from, to, false); + if (DashboardType.DISEASE.equals(dashboardDataProvider.getDashboardType())) + dashboardView.refreshDiseaseData(); + else + dashboardView.refreshDashboard(); }); btnThisWeek = createAndAddDateFilterButton( @@ -272,7 +324,11 @@ private HorizontalLayout createDateFilterButtonsLayout() { Date to = now; setDateFilter(from, to); btnCurrentPeriod.setCaption(btnThisWeek.getCaption()); - updateComparisonButtons(DateFilterType.THIS_WEEK, from, to, false); + updateComparisonButtons(NewDateFilterType.THIS_WEEK, from, to, false); + if (DashboardType.DISEASE.equals(dashboardDataProvider.getDashboardType())) + dashboardView.refreshDiseaseData(); + else + dashboardView.refreshDashboard(); }); btnLastWeek = createAndAddDateFilterButton( @@ -288,7 +344,11 @@ private HorizontalLayout createDateFilterButtonsLayout() { Date to = DateHelper.getEndOfWeek(DateHelper.subtractWeeks(now, 1)); setDateFilter(from, to); btnCurrentPeriod.setCaption(btnLastWeek.getCaption()); - updateComparisonButtons(DateFilterType.LAST_WEEK, from, to, false); + updateComparisonButtons(NewDateFilterType.LAST_WEEK, from, to, false); + if (DashboardType.DISEASE.equals(dashboardDataProvider.getDashboardType())) + dashboardView.refreshDiseaseData(); + else + dashboardView.refreshDashboard(); }); btnThisYear = createAndAddDateFilterButton( @@ -304,7 +364,11 @@ private HorizontalLayout createDateFilterButtonsLayout() { Date to = now; setDateFilter(from, to); btnCurrentPeriod.setCaption(btnThisYear.getCaption()); - updateComparisonButtons(DateFilterType.THIS_YEAR, from, to, false); + updateComparisonButtons(NewDateFilterType.THIS_YEAR, from, to, false); + if (DashboardType.DISEASE.equals(dashboardDataProvider.getDashboardType())) + dashboardView.refreshDiseaseData(); + else + dashboardView.refreshDashboard(); }); layout.addComponents(btnShowCustomPeriod, btnToday, btnYesterday, btnThisWeek, btnLastWeek, btnThisYear); @@ -375,7 +439,11 @@ private HorizontalLayout createCustomDateFilterLayout() { DateHelper.getEpiWeekYearBefore(fromWeek).toShortString() + " - " + DateHelper.getEpiWeekYearBefore(toWeek).toShortString())); } - updateComparisonButtons(DateFilterType.CUSTOM, null, null, true); + updateComparisonButtons(NewDateFilterType.CUSTOM, null, null, true); + if (DashboardType.DISEASE.equals(dashboardDataProvider.getDashboardType())) + dashboardView.refreshDiseaseData(); + else + dashboardView.refreshDashboard(); } else { if (dateFilterOption == DateFilterOption.DATE) { new Notification( @@ -406,6 +474,10 @@ private Component createDateComparisonButtonsLayout() { activeComparisonButton = btnPeriodBefore; updateComparisonDates(); btnComparisonPeriod.setCaption(btnPeriodBefore.getCaption()); + if (DashboardType.DISEASE.equals(dashboardDataProvider.getDashboardType())) + dashboardView.refreshDiseaseData(); + else + dashboardView.refreshDashboard(); }); btnPeriodLastYear = createAndAddDateFilterButton(Captions.dashboardSameDayLastYear, null, dateComparisonButtons); @@ -413,6 +485,10 @@ private Component createDateComparisonButtonsLayout() { activeComparisonButton = btnPeriodLastYear; updateComparisonDates(); btnComparisonPeriod.setCaption(btnPeriodLastYear.getCaption()); + if (DashboardType.DISEASE.equals(dashboardDataProvider.getDashboardType())) + dashboardView.refreshDiseaseData(); + else + dashboardView.refreshDashboard(); }); layout.addComponents(btnPeriodBefore, btnPeriodLastYear); @@ -449,7 +525,7 @@ private void changeCustomDateFilterPanelStyle(Button activeFilterButton, Set. + */ +package de.symeda.sormas.ui.dashboard.diseasedetails; + +import com.vaadin.shared.ui.ContentMode; +import com.vaadin.ui.*; +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.i18n.Strings; +import de.symeda.sormas.ui.dashboard.DashboardDataProvider; +import de.symeda.sormas.ui.utils.CssStyles; + +public class DiseaseDetailsComponent extends CssLayout { + + private static final long serialVersionUID = 1L; + + private DashboardDataProvider dashboardDataProvider; + private static final String HTML_DIV_END = ""; + + public DiseaseDetailsComponent(DashboardDataProvider dashboardDataProvider) { + + this.dashboardDataProvider = dashboardDataProvider; + addStyleName("disease-detail-card-display-top"); + } + + public void refresh(){ + + addTopLayout( + dashboardDataProvider.getDiseaseBurdenDetail().getDisease(), + dashboardDataProvider.getDiseaseBurdenDetail().getCaseCount(), + dashboardDataProvider.getDiseaseBurdenDetail().getOutbreakDistrictCount() > 0); + + addStatsLayout( + dashboardDataProvider.getDiseaseBurdenDetail().getCaseDeathCount(), + dashboardDataProvider.getDiseaseBurdenDetail().getCaseCount(), + dashboardDataProvider.getDiseaseBurdenDetail().getOutbreakDistrict(), + dashboardDataProvider.getDiseaseBurdenDetail().getLastReportedDistrictName(), + dashboardDataProvider.getDiseaseBurdenDetail().getDisease()); + } + + private void addTopLayout(Disease disease, Long casesCount, boolean isOutbreak) { + + VerticalLayout layout = new VerticalLayout(); + layout.setMargin(true); + layout.setSpacing(false); + CssStyles.style(layout, CssStyles.getDiseaseColor(disease)); + layout.setHeight(200, Unit.PIXELS); + layout.setWidth(250, Unit.PIXELS); + + HorizontalLayout nameAndOutbreakLayout = new HorizontalLayout(); + nameAndOutbreakLayout.setMargin(false); + nameAndOutbreakLayout.setSpacing(false); + nameAndOutbreakLayout.setHeight(90, Unit.PIXELS); + nameAndOutbreakLayout.setWidth(200, Unit.PIXELS); + + HorizontalLayout nameLayout = new HorizontalLayout(); + nameLayout.setMargin(false); + nameLayout.setSpacing(false); + nameLayout.setHeight(50, Unit.PIXELS); + nameLayout.setWidth(200, Unit.PIXELS); + Label nameLabel = new Label(disease.toShortString()); + nameLabel.setSizeUndefined(); + nameLabel.setHeight(20, Unit.PIXELS); + + CssStyles.style( + nameLabel, + CssStyles.LABEL_WHITE, + nameLabel.getValue().length() > 12 ? CssStyles.LABEL_SMALL : CssStyles.LABEL_WHITE, + CssStyles.LABEL_LARGE, + CssStyles.ALIGN_CENTER, + CssStyles.LABEL_UPPERCASE); + nameLayout.addComponent(nameLabel); + nameLayout.setComponentAlignment(nameLabel, Alignment.MIDDLE_CENTER); + nameAndOutbreakLayout.addComponent(nameLayout); + nameAndOutbreakLayout.setExpandRatio(nameLayout, 1); + + if (isOutbreak) { + HorizontalLayout outbreakLayout = new HorizontalLayout(); + outbreakLayout.setMargin(false); + outbreakLayout.setSpacing(false); + outbreakLayout.setHeight(15, Unit.PIXELS); + outbreakLayout.setWidth(100, Unit.PIXELS); + Label outbreakLabel = new Label(I18nProperties.getCaption(Captions.dashboardOutbreak).toUpperCase(), ContentMode.HTML); + outbreakLabel.setStyleName("disease-detail-outbreak-display", true); + + outbreakLayout.addComponent(outbreakLabel); + nameAndOutbreakLayout.addComponent(outbreakLayout); + } + + layout.addComponent(nameAndOutbreakLayout); + layout.setExpandRatio(nameAndOutbreakLayout, 1); + + HorizontalLayout countLayout = new HorizontalLayout(); + countLayout.setMargin(false); + countLayout.setSpacing(false); + CssStyles.style(countLayout, CssStyles.getDiseaseColor(disease)); + countLayout.setHeight(40, Unit.PIXELS); + countLayout.setWidth(70, Unit.PIXELS); + + Label countLabel = new Label("", ContentMode.HTML); + countLabel.setValue( + "

" + + casesCount.toString() + HTML_DIV_END); + + countLayout.addComponent(countLabel); + countLayout.setComponentAlignment(countLabel, Alignment.MIDDLE_CENTER); + + layout.addComponent(countLayout); + layout.setComponentAlignment(countLayout, Alignment.BOTTOM_CENTER); + layout.setExpandRatio(countLayout, 0.65f); + + addComponent(layout); + } + + private void addStatsLayout(Long fatalities, Long totalCase, String outbreakDistrict, String district, Disease disease) { + + VerticalLayout layout = new VerticalLayout(); + layout.setWidth(250, Unit.PIXELS); + layout.setHeight(120, Unit.PIXELS); + layout.setMargin(false); + layout.setSpacing(false); + + CssStyles.style(layout, CssStyles.getDiseaseColor(disease), CssStyles.BACKGROUND_DARKER); + float cfrPercent = calculateCfr(fatalities, totalCase); + + layout.addComponent(createDeathCfrItem(I18nProperties.getCaption(Captions.dashboardFatalities)+": ", + fatalities.toString()+"", fatalities > 0, + I18nProperties.getCaption(Captions.DiseaseBurden_caseFatalityRate)+": ", + String.valueOf(cfrPercent))); + + HorizontalLayout statsItem = createStatsItem( + I18nProperties.getCaption(Captions.dashboardLastReportedDistrict) + ": ", + district.length() == 0 ? I18nProperties.getString(Strings.none) : district, + false, + district.length() > 10 + ); + + CssStyles.style(statsItem, CssStyles.VSPACE_TOP_4, CssStyles.LABEL_WHITE); + layout.addComponent(statsItem); + + statsItem = createStatsItem( + I18nProperties.getCaption(Captions.DiseaseBurden_outbreakDistrictCount)+": ", + outbreakDistrict.length() == 0 ? I18nProperties.getString(Strings.none) : outbreakDistrict, + false, + outbreakDistrict.length() > 10 + ); + + CssStyles.style(statsItem, CssStyles.VSPACE_4, CssStyles.LABEL_WHITE); + layout.addComponent(statsItem); + addComponent(layout); + } + + private HorizontalLayout createDeathCfrItem(String fatalityLabel, String fatalityValue, + boolean isCritical, String cfrLabel, String cfrValue ) { + + HorizontalLayout layout = new HorizontalLayout(); + layout.setMargin(false); + layout.setSpacing(true); + + Label fatalityNameLabel = new Label("", ContentMode.HTML); + CssStyles.style(fatalityNameLabel, CssStyles.LABEL_WHITE, CssStyles.LABEL_PRIMARY, isCritical ? CssStyles.LABEL_CRITICAL : "", CssStyles.HSPACE_LEFT_3); + + fatalityNameLabel.setValue("
" + fatalityLabel + HTML_DIV_END + + "
" + fatalityValue+ HTML_DIV_END); + + layout.addComponent(fatalityNameLabel); + layout.setExpandRatio(fatalityNameLabel, 1); + + Label cfrNameLabel = new Label("", ContentMode.HTML); + CssStyles.style( + cfrNameLabel, + CssStyles.LABEL_WHITE, + CssStyles.LABEL_PRIMARY, + cfrValue.length() > 10 ? CssStyles.LABEL_SMALL : CssStyles.LABEL_WHITE, + isCritical ? CssStyles.LABEL_CRITICAL : ""); + + cfrNameLabel.setValue("
" + cfrLabel + HTML_DIV_END + + "
" + cfrValue + HTML_DIV_END); + + layout.addComponent(cfrNameLabel); + + return layout; + } + + private HorizontalLayout createStatsItem(String label, String value, boolean isCritical, boolean singleColumn) { + + HorizontalLayout layout = new HorizontalLayout(); + layout.setWidth(250, Unit.PIXELS); + layout.setMargin(false); + layout.setSpacing(false); + + Label nameLabel = new Label(label); + CssStyles.style( + nameLabel, + CssStyles.LABEL_WHITE, + CssStyles.LABEL_PRIMARY, isCritical ? CssStyles.LABEL_CRITICAL : "", + CssStyles.HSPACE_LEFT_3, + CssStyles.LABEL_IMPORTANT + ); + layout.addComponent(nameLabel); + + if (!singleColumn) { + layout.setExpandRatio(nameLabel, 1); + } + + Label valueLabel = new Label(value); + CssStyles.style( + valueLabel, + CssStyles.LABEL_WHITE, + CssStyles.LABEL_PRIMARY, + value.length() > 16 ? CssStyles.LABEL_SMALL : CssStyles.LABEL_WHITE, + isCritical ? CssStyles.LABEL_CRITICAL : "", + singleColumn ? CssStyles.HSPACE_LEFT_5 : CssStyles.ALIGN_CENTER); + + layout.addComponent(valueLabel); + + layout.setExpandRatio(valueLabel, singleColumn ? 1f : 0.65f); + layout.setComponentAlignment(valueLabel, Alignment.MIDDLE_CENTER); + return layout; + } + + private float calculateCfr(long fatalities, long totalCaseCount){ + + if (fatalities == 0 ) + return 0; + return ((float) fatalities / totalCaseCount) * 100; + } +} \ No newline at end of file diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/diseasedetails/DiseaseDetailsView.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/diseasedetails/DiseaseDetailsView.java new file mode 100644 index 00000000000..f76969cd746 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/diseasedetails/DiseaseDetailsView.java @@ -0,0 +1,177 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2022 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.dashboard.diseasedetails; + +import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.FacadeProvider; +import de.symeda.sormas.api.caze.CaseClassification; +import de.symeda.sormas.api.caze.NewCaseDateType; +import de.symeda.sormas.api.dashboard.NewDateFilterType; +import de.symeda.sormas.api.infrastructure.region.RegionDto; +import de.symeda.sormas.ui.dashboard.AbstractDashboardView; +import de.symeda.sormas.ui.dashboard.DashboardDataProvider; +import de.symeda.sormas.ui.dashboard.DashboardType; +import static com.vaadin.navigator.ViewChangeListener.*; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.logging.Level; +import java.util.logging.Logger; + +@SuppressWarnings("serial") +public class DiseaseDetailsView extends AbstractDashboardView { + + private static final long serialVersionUID = -1L; + public static final String VIEW_NAME = ROOT_VIEW_NAME + "/disease"; + + private static final Logger LOGGER = Logger.getLogger(DiseaseDetailsView.class.getName()); + + protected DiseaseDetailsViewLayout diseaseDetailsViewLayout; + + private static String diseaseDetailsData; + public static void setDiseaseDetailsData(String newData) { + + diseaseDetailsData =newData; + } + + public DiseaseDetailsView() { + + super(VIEW_NAME); + + dashboardDataProvider = new DashboardDataProvider(); + dashboardLayout.setSpacing(false); + if (dashboardDataProvider.getDashboardType() == null) { + dashboardDataProvider.setDashboardType(DashboardType.DISEASE); + } + + dashboardDataProvider.setDisease(getDiseases()); + + if (diseaseDetailsData != null) { + String[] dataParts = diseaseDetailsData.split("/"); + if (dataParts.length == 6) { + String dateFrom = dataParts[0]; + String dateTo = dataParts[1]; + String newDateFilterType = dataParts[2]; + String caseClassification = dataParts[3]; + String newCaseDateType = dataParts[4]; + String regionId = dataParts[5]; + setDateFilters(dateFrom, dateTo); + setDateFilterType(newDateFilterType); + setCaseClassification(caseClassification); + setNewCaseDateType(newCaseDateType); + setRegion(regionId); + } + } + + if (DashboardType.DISEASE.equals(dashboardDataProvider.getDashboardType())) { + dashboardDataProvider.setDisease(FacadeProvider.getDiseaseConfigurationFacade().getDefaultDisease()); + } + + dashboardSwitcher.setValue(DashboardType.DISEASE); + dashboardSwitcher.addValueChangeListener(e -> { + dashboardDataProvider.setDashboardType((DashboardType) e.getProperty().getValue()); + navigateToDashboardView(e); + }); + + // Added Component + diseaseDetailsViewLayout = new DiseaseDetailsViewLayout(dashboardDataProvider); + dashboardLayout.addComponent(diseaseDetailsViewLayout); + dashboardLayout.setExpandRatio(diseaseDetailsViewLayout, 1); + } + + private void setDateFilters(String dateFrom, String dateTo) { + + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + try { + if (dateFrom != null) { + Date startDate = dateFormat.parse(dateFrom); + dashboardDataProvider.setFromDate(startDate); + } + if (dateTo != null) { + Date endDate = dateFormat.parse(dateTo); + dashboardDataProvider.setToDate(endDate); + } + } catch (ParseException e) { + LOGGER.log(Level.SEVERE, "Date parsing error", e); + } + } + + private void setDateFilterType(String newDateFilterType) { + + try { + if(newDateFilterType!=null&&!newDateFilterType.equals("null")) { + NewDateFilterType filterType = NewDateFilterType.valueOf(newDateFilterType); + dashboardDataProvider.setDateFilterType(filterType); + } + } catch (IllegalArgumentException e) { + LOGGER.log(Level.WARNING, "Unsupported date filter type: " + newDateFilterType, e); + } + } + + private void setCaseClassification(String caseClassification) { + + try { + if(caseClassification!=null && !caseClassification.equals("null")) { + CaseClassification classification = CaseClassification.valueOf(caseClassification.replace(" ", "_").toUpperCase().trim()); + dashboardDataProvider.setCaseClassification(classification); + } + } catch (IllegalArgumentException e) { + LOGGER.log(Level.WARNING, "Unsupported case classification: " + caseClassification, e); + dashboardDataProvider.setCaseClassification(CaseClassification.NOT_CLASSIFIED); + } + } + + private void setNewCaseDateType(String newCaseDateType) { + + NewCaseDateType caseDateType; + switch (newCaseDateType) { + case "Symptom onset date": + caseDateType = NewCaseDateType.ONSET; + break; + case "Case report date": + caseDateType = NewCaseDateType.REPORT; + break; + default: + caseDateType = NewCaseDateType.MOST_RELEVANT; + } + dashboardDataProvider.setNewCaseDateType(caseDateType); + } + + private void setRegion(String regionId) { + + if (regionId != null && !"null".equals(regionId) && !regionId.isEmpty()) { + RegionDto region = FacadeProvider.getRegionFacade().getByUuid(regionId); + dashboardDataProvider.setRegion(region.toReference()); + } + } + + @Override + public void refreshDiseaseData() { + + super.refreshDiseaseData(); + + if (diseaseDetailsViewLayout != null) + diseaseDetailsViewLayout.refresh(); + } + + @Override + public void enter(ViewChangeEvent event) { + + super.enter(event); + dashboardDataProvider.setDisease(Disease.valueOf(event.getParameters())); + refreshDiseaseData(); + } +} \ No newline at end of file diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/diseasedetails/DiseaseDetailsViewLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/diseasedetails/DiseaseDetailsViewLayout.java new file mode 100644 index 00000000000..41541ac5630 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/diseasedetails/DiseaseDetailsViewLayout.java @@ -0,0 +1,133 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2022 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.dashboard.diseasedetails; + +import com.vaadin.shared.ui.MarginInfo; +import com.vaadin.ui.Alignment; +import com.vaadin.ui.Button; +import com.vaadin.ui.CustomLayout; +import com.vaadin.ui.HorizontalLayout; +import de.symeda.sormas.api.i18n.Captions; +import de.symeda.sormas.api.i18n.I18nProperties; +import de.symeda.sormas.ui.dashboard.DashboardDataProvider; +import de.symeda.sormas.ui.dashboard.map.DashboardMapComponent; +import de.symeda.sormas.ui.dashboard.surveillance.components.disease.tile.RegionalDiseaseBurdenGrid; +import de.symeda.sormas.ui.utils.LayoutUtil; + +public class DiseaseDetailsViewLayout extends CustomLayout { + + private static final long serialVersionUID = 6582975657305031105L; + + private static final String CARD = "card"; + private static final String GRID_TABLE = "table"; + private static final String GRID_VIEW_MORE = "viewMore"; + private static final String MAP = "map"; + private final DiseaseDetailsComponent diseaseDetailsComponent; + private final RegionalDiseaseBurdenGrid regionalDiseaseBurdenGrid; + private final DashboardMapComponent dashboardMapComponent; + + private boolean isShowMore; + private Button button; + public DiseaseDetailsViewLayout(DashboardDataProvider dashboardDataProvider) { + + setWidth(100, Unit.PERCENTAGE); + setTemplateContents( + LayoutUtil.fluidRow( + LayoutUtil.fluidColumnLoc(2, 0, 0, 0, CARD), + LayoutUtil.fluidColumnLoc(7, 1, 0, 0, GRID_TABLE), + LayoutUtil.fluidColumnLoc(6, 4, 0, 0, GRID_VIEW_MORE), + LayoutUtil.fluidColumnLoc(12, 0, 5, 0, MAP) + + ) + ); //NOTE span is heigth size, offset is row down + + isShowMore = false; + + diseaseDetailsComponent = new DiseaseDetailsComponent(dashboardDataProvider); + regionalDiseaseBurdenGrid = new RegionalDiseaseBurdenGrid(dashboardDataProvider); + dashboardMapComponent = new DashboardMapComponent(dashboardDataProvider); + + reload(); + } + + public void refresh() { + + diseaseDetailsComponent.removeAllComponents(); + diseaseDetailsComponent.refresh(); + regionalDiseaseBurdenGrid.refresh(); + dashboardMapComponent.refreshMap(); + } + + public void reload() { + + // Disease Card Layout + HorizontalLayout diseaseCardLayout = new HorizontalLayout(); + diseaseCardLayout.setWidth(50, Unit.PERCENTAGE); + diseaseCardLayout.setMargin(true); + diseaseCardLayout.setSpacing(true); + diseaseCardLayout.addComponent(diseaseDetailsComponent); + addComponent(diseaseCardLayout, CARD); + + + // Grid card layout + HorizontalLayout diseaseGridLayout = new HorizontalLayout(); + diseaseGridLayout.setWidth(400, Unit.PIXELS); + regionalDiseaseBurdenGrid.setHeight(320, Unit.PIXELS); + diseaseGridLayout.setMargin(false); + diseaseGridLayout.setSpacing(false); + diseaseGridLayout.addComponents(regionalDiseaseBurdenGrid); + addComponent(diseaseGridLayout, GRID_TABLE); + + viewMoreLayout(I18nProperties.getCaption(Captions.viewMore)); + button.addClickListener(event -> { + if(!isShowMore) { + regionalDiseaseBurdenGrid.setHeight(750, Unit.PIXELS); + isShowMore = true; + }else { + regionalDiseaseBurdenGrid.setHeight(320, Unit.PIXELS); + isShowMore = false; + } + }); + +// Map layout + HorizontalLayout mapLayout = new HorizontalLayout(); + mapLayout.setWidth(100, Unit.PERCENTAGE); + final int BASE_HEIGHT = 600; + mapLayout.setHeight(BASE_HEIGHT, Unit.PIXELS); + mapLayout.setSpacing(false); + + dashboardMapComponent.setMargin(false); + dashboardMapComponent.setSpacing(false); + mapLayout.setMargin(new MarginInfo(true, true, false, true)); + dashboardMapComponent.addStyleName("map-border-layout"); + mapLayout.addComponent(dashboardMapComponent); + addComponent(mapLayout, MAP); + } + + public HorizontalLayout viewMoreLayout(String title){ + + HorizontalLayout viewMoreLayout = new HorizontalLayout(); + viewMoreLayout.setMargin(false); + viewMoreLayout.setSpacing(false); + button = new Button(title); + viewMoreLayout.setHeight(5, Unit.PIXELS); + button.setHeight(30, Unit.PIXELS); + viewMoreLayout.addComponent(button); + viewMoreLayout.setComponentAlignment(button, Alignment.TOP_CENTER); + addComponent(viewMoreLayout, GRID_VIEW_MORE); + + return viewMoreLayout; + } +} \ No newline at end of file diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/map/BaseDashboardMapComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/map/BaseDashboardMapComponent.java index 41a90cd99a5..30d09125fa1 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/map/BaseDashboardMapComponent.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/map/BaseDashboardMapComponent.java @@ -49,6 +49,7 @@ import de.symeda.sormas.ui.map.MarkerIcon; import de.symeda.sormas.ui.utils.ButtonHelper; import de.symeda.sormas.ui.utils.CssStyles; +import de.symeda.sormas.ui.dashboard.DashboardType; public abstract class BaseDashboardMapComponent, P extends AbstractDashboardDataProvider> extends VerticalLayout { @@ -59,10 +60,10 @@ public abstract class BaseDashboardMapComponent { @@ -119,12 +136,131 @@ public class DashboardMapComponent extends BaseDashboardMapComponent externalExpandListener; private boolean emptyPopulationDistrictPresent; + private MapCasePeriodOption mapCasePeriodOption; + private Label overlayMessageLabel; + private PeriodFilterReloadFlag reloadPeriodFiltersFlag = PeriodFilterReloadFlag.RELOAD_AND_KEEP_VALUE; + + ComboBox cmbPeriodFilter; + ComboBox cmbPeriodType; + + private Date dateFrom = null; + private Date dateTo = null; + private enum PeriodFilterReloadFlag { + RELOAD_AND_KEEP_VALUE, + RELOAD_AND_CLEAR_VALUE, + DONT_RELOAD + } public DashboardMapComponent(DashboardDataProvider dashboardDataProvider) { super( dashboardDataProvider.getDashboardType() == DashboardType.SURVEILLANCE ? Strings.headingCaseStatusMap : Strings.headingContactMap, dashboardDataProvider, null); + if(dashboardDataProvider.getDashboardType().equals(DashboardType.DISEASE)) { + + setMargin(false); + setSpacing(false); + setSizeFull(); + + map = new LeafletMap(); + + map.setSizeFull(); + map.addMarkerClickListener(event -> onMarkerClicked(event.getGroupId(), event.getMarkerIndex())); + + { + GeoShapeProvider geoShapeProvider = FacadeProvider.getGeoShapeProvider(); + + final GeoLatLon mapCenter; + // If map.usecountrycenter=true, use config coordinates. Else try to calculate the center of the user region/country + if (FacadeProvider.getConfigFacade().isMapUseCountryCenter()) { + mapCenter = FacadeProvider.getConfigFacade().getCountryCenter(); + map.setCenter(mapCenter); + } else { + UserDto user = UserProvider.getCurrent().getUser(); + if (user.getRegion() != null) { + mapCenter = geoShapeProvider.getCenterOfRegion(user.getRegion()); + } else { + mapCenter = geoShapeProvider.getCenterOfAllRegions(); + } + + GeoLatLon center = Optional.ofNullable(mapCenter).orElseGet(FacadeProvider.getConfigFacade()::getCountryCenter); + map.setCenter(center); + } + + GeoLatLon center = Optional.ofNullable(mapCenter).orElseGet(FacadeProvider.getConfigFacade()::getCountryCenter); + if (center == null || (center.getLat() == 0.0 && center.getLon() == 0)) { + center = new GeoLatLon(8.134, 1.423); + } + map.setCenter(center); + } + + map.setZoom(FacadeProvider.getConfigFacade().getMapZoom()); + + if (dashboardDataProvider.getDashboardType() == DashboardType.SURVEILLANCE) { + showCases = true; + caseClassificationOption = MapCaseClassificationOption.ALL_CASES; + mapCasePeriodOption = MapCasePeriodOption.CASES_INCIDENCE; + showContacts = false; + showEvents = false; + showConfirmedContacts = true; + showUnconfirmedContacts = true; + } else if (dashboardDataProvider.getDashboardType() == DashboardType.CONTACTS) { + showCases = false; + caseClassificationOption = MapCaseClassificationOption.ALL_CASES; + mapCasePeriodOption = MapCasePeriodOption.CASES_INCIDENCE; + showContacts = true; + showEvents = false; + showConfirmedContacts = true; + showUnconfirmedContacts = true; + } else if (dashboardDataProvider.getDashboardType() == DashboardType.DISEASE) { + map.setZoom(6); + showCases = true; + caseClassificationOption = MapCaseClassificationOption.ALL_CASES; + showContacts = false; + showEvents = false; + showConfirmedContacts = true; + showUnconfirmedContacts = true; + } + hideOtherCountries = false; + showCurrentEpiSituation = false; + + this.setMargin(true); + + // Add components + addComponent(createMapHeader()); + + CssLayout mapLayout = new CssLayout(); + mapLayout.setSizeFull(); + mapLayout.setStyleName(DashboardCssStyles.MAP_CONTAINER); + + map.addStyleName(DashboardCssStyles.MAP_COMPONENT); + mapLayout.addComponent(map); + + overlayBackground = new CssLayout(); + overlayBackground.setStyleName(DashboardCssStyles.MAP_OVERLAY_BACKGROUND); + overlayBackground.setVisible(false); + mapLayout.addComponent(overlayBackground); + + overlayMessageLabel = new Label(); + overlayMessageLabel.addStyleNames(CssStyles.ALIGN_CENTER, CssStyles.LABEL_WHITE, CssStyles.LABEL_WHITE_SPACE_NORMAL); + + Button button = ButtonHelper.createButton(Captions.showPlacesOnMap, (e) -> refreshMapDashboard(true)); + + overlayLayout = new VerticalLayout(overlayMessageLabel, button); + overlayLayout.setStyleName(DashboardCssStyles.MAP_OVERLAY); + overlayLayout.setHeightFull(); + overlayLayout.setComponentAlignment(overlayMessageLabel, Alignment.MIDDLE_CENTER); + overlayLayout.setExpandRatio(overlayMessageLabel, 0); + overlayLayout.setComponentAlignment(button, Alignment.MIDDLE_CENTER); + overlayLayout.setExpandRatio(button, 0); + overlayLayout.setVisible(false); + mapLayout.addComponent(overlayLayout); + + addComponent(mapLayout); + setExpandRatio(mapLayout, 1); + + addComponent(createMapFooter()); + } } @Override @@ -151,25 +287,569 @@ protected void addComponents() { super.addComponents(); } - protected void refreshMap(boolean forced) { - clearRegionShapes(); - clearCaseMarkers(); - clearContactMarkers(); - clearEventMarkers(); - LeafletMapUtil.clearOtherCountriesOverlay(map); - if (hideOtherCountries) { - LeafletMapUtil.addOtherCountriesOverlay(map); + private HorizontalLayout createMapFooter() { + HorizontalLayout mapFooterLayout = new HorizontalLayout(); + mapFooterLayout.setWidth(100, Unit.PERCENTAGE); + mapFooterLayout.setSpacing(true); + CssStyles.style(mapFooterLayout, CssStyles.VSPACE_4, CssStyles.VSPACE_TOP_3); + + // Map key dropdown button + legendDropdown = ButtonHelper.createPopupButton(Captions.dashboardMapKey, null, CssStyles.BUTTON_SUBTLE); + legendDropdown.setContent(createMapLegend()); + + mapFooterLayout.addComponent(legendDropdown); + mapFooterLayout.setComponentAlignment(legendDropdown, Alignment.MIDDLE_RIGHT); + mapFooterLayout.setExpandRatio(legendDropdown, 1); + + // Layers dropdown button + VerticalLayout layersLayout = new VerticalLayout(); + { + layersLayout.setMargin(true); + layersLayout.setSpacing(false); + layersLayout.setSizeUndefined(); + + // Add check boxes and apply button + { + //case classifications + OptionGroup caseClassificationOptions = new OptionGroup(); + caseClassificationOptions.addItems((Object[]) MapCaseClassificationOption.values()); + caseClassificationOptions.setValue(caseClassificationOption); + caseClassificationOptions.addValueChangeListener(event -> { + caseClassificationOption = (MapCaseClassificationOption) event.getProperty().getValue(); + refreshMapDashboard(true); + }); + + // Optiongroup to select what property the coordinates should be based on + OptionGroup mapCaseDisplayModeSelect = new OptionGroup(); + mapCaseDisplayModeSelect.setWidth(100, Unit.PERCENTAGE); + mapCaseDisplayModeSelect.addItems((Object[]) MapCaseDisplayMode.values()); + mapCaseDisplayModeSelect.setValue(mapCaseDisplayMode); + mapCaseDisplayModeSelect.addValueChangeListener(event -> { + mapCaseDisplayMode = (MapCaseDisplayMode) event.getProperty().getValue(); + refreshMapDashboard(true); + }); + + HorizontalLayout showCasesLayout = new HorizontalLayout(); + { + showCasesLayout.setMargin(false); + showCasesLayout.setSpacing(false); + CheckBox showCasesCheckBox = new CheckBox(); + showCasesCheckBox.setId(Captions.dashboardShowCases); + showCasesCheckBox.setCaption(I18nProperties.getCaption(Captions.dashboardShowCases)); + showCasesCheckBox.setValue(showCases); + showCasesCheckBox.addValueChangeListener(e -> { + showCases = (boolean) e.getProperty().getValue(); + mapCaseDisplayModeSelect.setEnabled(showCases); + caseClassificationOptions.setEnabled(showCases); + mapCaseDisplayModeSelect.setValue(mapCaseDisplayMode); + caseClassificationOptions.setEnabled(showCases); + refreshMapDashboard(true); + }); + showCasesLayout.addComponent(showCasesCheckBox); + + Label infoLabel = new Label(VaadinIcons.INFO_CIRCLE.getHtml(), ContentMode.HTML); + infoLabel.setDescription(I18nProperties.getString(Strings.infoCaseMap)); + CssStyles.style(infoLabel, CssStyles.LABEL_MEDIUM, CssStyles.LABEL_SECONDARY, CssStyles.HSPACE_LEFT_3); + infoLabel.setHeightUndefined(); + showCasesLayout.addComponent(infoLabel); + showCasesLayout.setComponentAlignment(infoLabel, Alignment.TOP_CENTER); + } + layersLayout.addComponent(showCasesLayout); + + layersLayout.addComponent(caseClassificationOptions); + caseClassificationOptions.setEnabled(showCases); + + layersLayout.addComponent(mapCaseDisplayModeSelect); + mapCaseDisplayModeSelect.setEnabled(showCases); + + layersLayout.addComponent(caseClassificationOptions); + caseClassificationOptions.setEnabled(showCases); + + CheckBox showConfirmedContactsCheckBox = new CheckBox(); + showConfirmedContactsCheckBox.setId(Captions.dashboardShowConfirmedContacts); + CheckBox showUnconfirmedContactsCheckBox = new CheckBox(); + showUnconfirmedContactsCheckBox.setId(Captions.dashboardShowUnconfirmedContacts); + + CheckBox showContactsCheckBox = new CheckBox(); + showContactsCheckBox.setId(Captions.dashboardShowContacts); + showContactsCheckBox.setCaption(I18nProperties.getCaption(Captions.dashboardShowContacts)); + showContactsCheckBox.setValue(showContacts); + showContactsCheckBox.addValueChangeListener(e -> { + showContacts = (boolean) e.getProperty().getValue(); + showConfirmedContactsCheckBox.setEnabled(showContacts); + showConfirmedContactsCheckBox.setValue(true); + showUnconfirmedContactsCheckBox.setEnabled(showContacts); + showUnconfirmedContactsCheckBox.setValue(true); + refreshMapDashboard(true); + }); + layersLayout.addComponent(showContactsCheckBox); + + showConfirmedContactsCheckBox.setCaption(I18nProperties.getCaption(Captions.dashboardShowConfirmedContacts)); + showConfirmedContactsCheckBox.setValue(showConfirmedContacts); + showConfirmedContactsCheckBox.addValueChangeListener(e -> { + showConfirmedContacts = (boolean) e.getProperty().getValue(); + refreshMapDashboard(true); + }); + layersLayout.addComponent(showConfirmedContactsCheckBox); + + CssStyles.style(showUnconfirmedContactsCheckBox, CssStyles.VSPACE_3); + showUnconfirmedContactsCheckBox.setCaption(I18nProperties.getCaption(Captions.dashboardShowUnconfirmedContacts)); + showUnconfirmedContactsCheckBox.setValue(showUnconfirmedContacts); + showUnconfirmedContactsCheckBox.addValueChangeListener(e -> { + showUnconfirmedContacts = (boolean) e.getProperty().getValue(); + refreshMapDashboard(true); + }); + layersLayout.addComponent(showUnconfirmedContactsCheckBox); + + showConfirmedContactsCheckBox.setEnabled(showContacts); + showUnconfirmedContactsCheckBox.setEnabled(showContacts); + + CheckBox showEventsCheckBox = new CheckBox(); + showEventsCheckBox.setId(Captions.dashboardShowEvents); + CssStyles.style(showEventsCheckBox, CssStyles.VSPACE_3); + showEventsCheckBox.setCaption(I18nProperties.getCaption(Captions.dashboardShowEvents)); + showEventsCheckBox.setValue(showEvents); + showEventsCheckBox.addValueChangeListener(e -> { + showEvents = (boolean) e.getProperty().getValue(); + refreshMapDashboard(true); + }); + layersLayout.addComponent(showEventsCheckBox); + if (nonNull(UserProvider.getCurrent()) && UserProvider.getCurrent().hasNationJurisdictionLevel()) { + OptionGroup regionMapVisualizationSelect = new OptionGroup(); + regionMapVisualizationSelect.setWidth(100, Unit.PERCENTAGE); + regionMapVisualizationSelect.addItems((Object[]) CaseMeasure.values()); + regionMapVisualizationSelect.setValue(caseMeasure); + regionMapVisualizationSelect.addValueChangeListener(event -> { + caseMeasure = (CaseMeasure) event.getProperty().getValue(); + refreshMapDashboard(true); + }); + + HorizontalLayout showRegionsLayout = new HorizontalLayout(); + { + showRegionsLayout.setMargin(false); + showRegionsLayout.setSpacing(false); + CheckBox showRegionsCheckBox = new CheckBox(); + showRegionsCheckBox.setId(Captions.dashboardShowRegions); + showRegionsCheckBox.setCaption(I18nProperties.getCaption(Captions.dashboardShowRegions)); + showRegionsCheckBox.setValue(showRegions); + showRegionsCheckBox.addValueChangeListener(e -> { + showRegions = (boolean) e.getProperty().getValue(); + regionMapVisualizationSelect.setEnabled(showRegions); + regionMapVisualizationSelect.setValue(caseMeasure); + refreshMapDashboard(true); + }); + showRegionsLayout.addComponent(showRegionsCheckBox); + + Label infoLabel = new Label(VaadinIcons.INFO_CIRCLE.getHtml(), ContentMode.HTML); + infoLabel.setDescription(I18nProperties.getString(Strings.infoCaseIncidence)); + CssStyles.style(infoLabel, CssStyles.LABEL_MEDIUM, CssStyles.LABEL_SECONDARY, CssStyles.HSPACE_LEFT_3); + infoLabel.setHeightUndefined(); + showRegionsLayout.addComponent(infoLabel); + showRegionsLayout.setComponentAlignment(infoLabel, Alignment.TOP_CENTER); + } + layersLayout.addComponent(showRegionsLayout); + layersLayout.addComponent(regionMapVisualizationSelect); + regionMapVisualizationSelect.setEnabled(showRegions); + } + + CheckBox hideOtherCountriesCheckBox = new CheckBox(); + hideOtherCountriesCheckBox.setId(Captions.dashboardHideOtherCountries); + hideOtherCountriesCheckBox.setCaption(I18nProperties.getCaption(Captions.dashboardHideOtherCountries)); + hideOtherCountriesCheckBox.setValue(hideOtherCountries); + hideOtherCountriesCheckBox.addValueChangeListener(e -> { + hideOtherCountries = (boolean) e.getProperty().getValue(); + refreshMapDashboard(true); + }); + CssStyles.style(hideOtherCountriesCheckBox, CssStyles.VSPACE_3); + layersLayout.addComponent(hideOtherCountriesCheckBox); + + CheckBox showCurrentEpiSituationCB = new CheckBox(); + showCurrentEpiSituationCB.setId(Captions.dashboardMapShowEpiSituation); + showCurrentEpiSituationCB.setCaption(I18nProperties.getCaption(Captions.dashboardMapShowEpiSituation)); + showCurrentEpiSituationCB.setValue(false); + showCurrentEpiSituationCB.addValueChangeListener(e -> { + showCurrentEpiSituation = (boolean) e.getProperty().getValue(); + refreshMapDashboard(true); + }); + layersLayout.addComponent(showCurrentEpiSituationCB); + + createPeriodFilters(layersLayout); + } } - Date fromDate = dashboardDataProvider.getFromDate(); - Date toDate = dashboardDataProvider.getToDate(); + PopupButton layersDropdown = ButtonHelper.createPopupButton(Captions.dashboardMapLayers, layersLayout, CssStyles.BUTTON_SUBTLE); - if (showRegions) { - showRegionsShapes(caseMeasure, fromDate, toDate, dashboardDataProvider.getDisease()); + mapFooterLayout.addComponent(layersDropdown); + mapFooterLayout.setComponentAlignment(layersDropdown, Alignment.MIDDLE_RIGHT); + + return mapFooterLayout; + } + + private VerticalLayout createMapLegend() { + VerticalLayout legendLayout = new VerticalLayout(); + legendLayout.setSpacing(false); + legendLayout.setMargin(true); + legendLayout.setSizeUndefined(); + + // Disable map key dropdown if no layers have been selected + if (showCases || showContacts || showEvents || showRegions) { + legendDropdown.setEnabled(true); + } else { + legendDropdown.setEnabled(false); + return legendLayout; + } + + // Health facilities + + // Cases + if (showCases) { + if (mapCaseDisplayMode == MapCaseDisplayMode.FACILITY || mapCaseDisplayMode == MapCaseDisplayMode.FACILITY_OR_CASE_ADDRESS) { + Label facilitiesKeyLabel = new Label(I18nProperties.getCaption(Captions.dashboardFacilities)); + CssStyles.style(facilitiesKeyLabel, CssStyles.H4, CssStyles.VSPACE_4, CssStyles.VSPACE_TOP_NONE); + legendLayout.addComponent(facilitiesKeyLabel); + + HorizontalLayout facilitiesKeyLayout = new HorizontalLayout(); + { + facilitiesKeyLayout.setSpacing(false); + facilitiesKeyLayout.setMargin(false); + HorizontalLayout legendEntry = + buildMarkerLegendEntry(MarkerIcon.FACILITY_UNCLASSIFIED, I18nProperties.getCaption(Captions.dashboardNotYetClassifiedOnly)); + CssStyles.style(legendEntry, CssStyles.HSPACE_RIGHT_3); + facilitiesKeyLayout.addComponent(legendEntry); + legendEntry = buildMarkerLegendEntry(MarkerIcon.FACILITY_SUSPECT, I18nProperties.getCaption(Captions.dashboardGt1SuspectCases)); + CssStyles.style(legendEntry, CssStyles.HSPACE_RIGHT_3); + facilitiesKeyLayout.addComponent(legendEntry); + legendEntry = buildMarkerLegendEntry(MarkerIcon.FACILITY_PROBABLE, I18nProperties.getCaption(Captions.dashboardGt1ProbableCases)); + CssStyles.style(legendEntry, CssStyles.HSPACE_RIGHT_3); + facilitiesKeyLayout.addComponent(legendEntry); + legendEntry = + buildMarkerLegendEntry(MarkerIcon.FACILITY_CONFIRMED, I18nProperties.getCaption(Captions.dashboardGt1ConfirmedCases)); + facilitiesKeyLayout.addComponent(legendEntry); + } + legendLayout.addComponent(facilitiesKeyLayout); + } + + Label casesKeyLabel = new Label(I18nProperties.getString(Strings.entityCases)); + if (mapCaseDisplayMode == MapCaseDisplayMode.FACILITY || mapCaseDisplayMode == MapCaseDisplayMode.FACILITY_OR_CASE_ADDRESS) { + CssStyles.style(casesKeyLabel, CssStyles.H4, CssStyles.VSPACE_4, CssStyles.VSPACE_TOP_3); + } else { + CssStyles.style(casesKeyLabel, CssStyles.H4, CssStyles.VSPACE_4, CssStyles.VSPACE_TOP_NONE); + } + legendLayout.addComponent(casesKeyLabel); + + HorizontalLayout casesKeyLayout = new HorizontalLayout(); + { + casesKeyLayout.setSpacing(false); + casesKeyLayout.setMargin(false); + HorizontalLayout legendEntry = + buildMarkerLegendEntry(MarkerIcon.CASE_UNCLASSIFIED, I18nProperties.getCaption(Captions.dashboardNotYetClassified)); + CssStyles.style(legendEntry, CssStyles.HSPACE_RIGHT_3); + casesKeyLayout.addComponent(legendEntry); + legendEntry = buildMarkerLegendEntry(MarkerIcon.CASE_SUSPECT, I18nProperties.getCaption(Captions.dashboardSuspect)); + CssStyles.style(legendEntry, CssStyles.HSPACE_RIGHT_3); + casesKeyLayout.addComponent(legendEntry); + legendEntry = buildMarkerLegendEntry(MarkerIcon.CASE_PROBABLE, I18nProperties.getCaption(Captions.dashboardProbable)); + CssStyles.style(legendEntry, CssStyles.HSPACE_RIGHT_3); + casesKeyLayout.addComponent(legendEntry); + legendEntry = buildMarkerLegendEntry(MarkerIcon.CASE_CONFIRMED, I18nProperties.getCaption(Captions.dashboardConfirmed)); + casesKeyLayout.addComponent(legendEntry); + } + legendLayout.addComponent(casesKeyLayout); + } + + // Contacts + if (showContacts) { + Label contactsKeyLabel = new Label(I18nProperties.getString(Strings.entityContacts)); + if (showCases) { + CssStyles.style(contactsKeyLabel, CssStyles.H4, CssStyles.VSPACE_4, CssStyles.VSPACE_TOP_3); + } else { + CssStyles.style(contactsKeyLabel, CssStyles.H4, CssStyles.VSPACE_4, CssStyles.VSPACE_TOP_NONE); + } + legendLayout.addComponent(contactsKeyLabel); + + HorizontalLayout contactsKeyLayout = new HorizontalLayout(); + { + contactsKeyLayout.setSpacing(false); + contactsKeyLayout.setMargin(false); + HorizontalLayout legendEntry = + buildMarkerLegendEntry(MarkerIcon.CONTACT_OK, I18nProperties.getCaption(Captions.dashboardNotAContact)); + CssStyles.style(legendEntry, CssStyles.HSPACE_RIGHT_3); + contactsKeyLayout.addComponent(legendEntry); + legendEntry = buildMarkerLegendEntry(MarkerIcon.CONTACT_OVERDUE, I18nProperties.getCaption(Captions.dashboardUnconfirmedContact)); + CssStyles.style(legendEntry, CssStyles.HSPACE_RIGHT_3); + contactsKeyLayout.addComponent(legendEntry); + legendEntry = buildMarkerLegendEntry(MarkerIcon.CONTACT_LONG_OVERDUE, I18nProperties.getCaption(Captions.dashboardConfirmedContact)); + contactsKeyLayout.addComponent(legendEntry); + } + legendLayout.addComponent(contactsKeyLayout); } - super.refreshMap(forced); + // Events + if (showEvents) { + Label eventsKeyLabel = new Label(I18nProperties.getString(Strings.entityEvents)); + if (showCases || showContacts) { + CssStyles.style(eventsKeyLabel, CssStyles.H4, CssStyles.VSPACE_4, CssStyles.VSPACE_TOP_3); + } else { + CssStyles.style(eventsKeyLabel, CssStyles.H4, CssStyles.VSPACE_4, CssStyles.VSPACE_TOP_NONE); + } + legendLayout.addComponent(eventsKeyLabel); + + HorizontalLayout eventsKeyLayout = new HorizontalLayout(); + { + eventsKeyLayout.setSpacing(false); + eventsKeyLayout.setMargin(false); + HorizontalLayout legendEntry = buildMarkerLegendEntry(MarkerIcon.EVENT_RUMOR, EventStatus.SIGNAL.toString()); + CssStyles.style(legendEntry, CssStyles.HSPACE_RIGHT_3); + eventsKeyLayout.addComponent(legendEntry); + legendEntry = buildMarkerLegendEntry(MarkerIcon.EVENT_OUTBREAK, EventStatus.EVENT.toString()); + eventsKeyLayout.addComponent(legendEntry); + } + legendLayout.addComponent(eventsKeyLayout); + } + + // Districts + if (showRegions && districtValuesLowerQuartile != null && districtValuesMedian != null && districtValuesUpperQuartile != null) { + Label districtsKeyLabel = new Label(I18nProperties.getString(Strings.entityDistricts)); + if (showCases || showContacts || showEvents) { + CssStyles.style(districtsKeyLabel, CssStyles.H4, CssStyles.VSPACE_4, CssStyles.VSPACE_TOP_3); + } else { + CssStyles.style(districtsKeyLabel, CssStyles.H4, CssStyles.VSPACE_4, CssStyles.VSPACE_TOP_NONE); + } + legendLayout.addComponent(districtsKeyLabel); + legendLayout.addComponent( + buildRegionLegend( + false, + caseMeasure, + emptyPopulationDistrictPresent, + districtValuesLowerQuartile, + districtValuesMedian, + districtValuesUpperQuartile, + InfrastructureHelper.CASE_INCIDENCE_DIVISOR)); + + Label descLabel = new Label(I18nProperties.getString(Strings.infoDashboardIncidence)); + CssStyles.style(descLabel, CssStyles.LABEL_SMALL); + legendLayout.addComponent(descLabel); + } + + return legendLayout; + } + + private void createPeriodFilters(VerticalLayout layersLayout) { + cmbPeriodType = new ComboBox(); + cmbPeriodFilter = new ComboBox(); + + Button btnBack = new Button(VaadinIcons.CHEVRON_LEFT); + Button btnForward = new Button(VaadinIcons.CHEVRON_RIGHT); + + cmbPeriodType.setItems(MapPeriodType.values()); + cmbPeriodType.setPlaceholder(I18nProperties.getString(Strings.promptFilterByPeriod)); + cmbPeriodType.setWidth(132, Unit.PIXELS); + cmbPeriodType.addValueChangeListener(e -> { + reloadPeriodFiltersFlag = PeriodFilterReloadFlag.RELOAD_AND_CLEAR_VALUE; + + updatePeriodFilters(); + }); + + //case period display + OptionGroup casePeriodDisplayOptions = new OptionGroup(); + casePeriodDisplayOptions.addItems((Object[]) MapCasePeriodOption.values()); + casePeriodDisplayOptions.setValue(mapCasePeriodOption); + casePeriodDisplayOptions.setEnabled(false); + casePeriodDisplayOptions.addValueChangeListener(event -> { + mapCasePeriodOption = (MapCasePeriodOption) event.getProperty().getValue(); + Date date = (Date) cmbPeriodFilter.getValue(); + if (date != null) { + if (mapCasePeriodOption.equals(MapCasePeriodOption.CASES_INCIDENCE)) { + dateFrom = DateHelper.getStartOfYear(date); + + } else { + dateFrom = DateHelper.getStartOfMonth(date); + + } + dateTo = DateHelper.getEndOfMonth(date); + reloadPeriodFiltersFlag = PeriodFilterReloadFlag.DONT_RELOAD; + dashboardDataProvider.setFromDate(dateFrom); + dashboardDataProvider.setToDate(dateTo); + refreshMapDashboard(); + } else { + refreshMapDashboard(false); + } + }); + + cmbPeriodFilter.setPlaceholder(I18nProperties.getString(Strings.promptSelectPeriod)); + cmbPeriodFilter.setWidth(120, Unit.PIXELS); + cmbPeriodFilter.setEmptySelectionAllowed(false); + cmbPeriodFilter.setEnabled(false); + cmbPeriodFilter.addValueChangeListener(e -> { + Date date = (Date) e.getValue(); + + if (date != null) { + MapPeriodType periodType = (MapPeriodType) cmbPeriodType.getValue(); + + switch (periodType) { + case DAILY: + dateFrom = DateHelper.getStartOfDay(date); + dateTo = DateHelper.getEndOfDay(date); + break; + case WEEKLY: + dateFrom = DateHelper.getStartOfWeek(date); + dateTo = DateHelper.getEndOfWeek(date); + break; + case MONTHLY: + dateFrom = DateHelper.getStartOfMonth(date); + dateTo = DateHelper.getEndOfMonth(date); + casePeriodDisplayOptions.setEnabled(true); + break; + case YEARLY: + dateFrom = DateHelper.getStartOfYear(date); + dateTo = DateHelper.getEndOfYear(date); + break; + default: + dateFrom = null; + dateTo = null; + } + } else { + dateFrom = null; + dateTo = null; + } + + //disable arrow buttons if date is first or last item in the dropdown + int curDateIndex = ((List) cmbPeriodFilter.getValue()).indexOf(date); + Boolean hasNextDate = !((List) cmbPeriodFilter.getValue()).isEmpty() && curDateIndex < ((List)cmbPeriodFilter.getValue()).size() - 1; + Boolean hasPrevDate = !((List)cmbPeriodFilter.getValue()).isEmpty() && curDateIndex > 0; + btnBack.setEnabled(hasPrevDate); + btnForward.setEnabled(hasNextDate); + + reloadPeriodFiltersFlag = PeriodFilterReloadFlag.DONT_RELOAD; + + refreshMapDashboard(); + }); + cmbPeriodFilter.addValueChangeListener(e -> { + cmbPeriodFilter.setEnabled(!((List)cmbPeriodFilter.getValue()).isEmpty()); + btnForward.setEnabled(!((List)cmbPeriodFilter.getValue()).isEmpty()); + }); + + CssStyles.style(btnBack, ValoTheme.BUTTON_BORDERLESS); + btnBack.setEnabled(false); + btnBack.addClickListener(e -> { + Date curDate = (Date) cmbPeriodFilter.getValue(); + int curDateIndex = ((List) cmbPeriodFilter.getValue()).indexOf(curDate); + + if (curDateIndex <= 0) + return; + + int prevDateIndex = curDateIndex - 1; + Date prevDate = (Date) ((List) cmbPeriodFilter.getValue()).get(prevDateIndex); + + cmbPeriodFilter.setValue(prevDate); + }); + + CssStyles.style(btnForward, ValoTheme.BUTTON_BORDERLESS); + btnForward.setEnabled(false); + btnForward.addClickListener(e -> { + Date curDate = (Date) cmbPeriodFilter.getValue(); + int curDateIndex = ((List) cmbPeriodFilter.getValue()).indexOf(curDate); + + if (curDateIndex >= ((List) cmbPeriodFilter.getValue()).size() - 1) + return; + + int nextDateIndex = curDateIndex + 1; + Date nextDate = (Date) ((List) cmbPeriodFilter.getValue()).get(nextDateIndex); + + cmbPeriodFilter.setValue(nextDate); + }); + + HorizontalLayout periodSelectionLayout = new HorizontalLayout(); + periodSelectionLayout.setSpacing(false); + + periodSelectionLayout.addComponent(btnBack); + periodSelectionLayout.addComponent(cmbPeriodFilter); + periodSelectionLayout.addComponent(btnForward); + + HorizontalLayout periodFilterLayout = new HorizontalLayout(); + periodFilterLayout.setStyleName(CssStyles.VSPACE_TOP_2); + periodFilterLayout.addComponent(cmbPeriodType); + periodFilterLayout.addComponent(periodSelectionLayout); + layersLayout.addComponent(periodFilterLayout); + layersLayout.addComponent(casePeriodDisplayOptions); + } + + private void updatePeriodFilters() { + MapPeriodType periodType = (MapPeriodType) cmbPeriodType.getValue(); + + //store current flag and reset it + PeriodFilterReloadFlag reloadFlag = reloadPeriodFiltersFlag; + reloadPeriodFiltersFlag = PeriodFilterReloadFlag.RELOAD_AND_KEEP_VALUE; + + String cachedDateValue = cmbPeriodFilter.getCaption(); + + if (reloadFlag != PeriodFilterReloadFlag.DONT_RELOAD) + cmbPeriodFilter.setItems(); + + if (periodType == null) { + cmbPeriodFilter.setEnabled(false); + dateFrom = null; + dateTo = null; + + if (reloadFlag != PeriodFilterReloadFlag.RELOAD_AND_KEEP_VALUE) + refreshMapDashboard(); + + return; + } + + cmbPeriodFilter.setEnabled(true); + + if (mapAndFacilityCases.isEmpty()) + return; + + + List reportedDates = mapAndFacilityCases.stream().map(c -> c.getReportDate()).collect(Collectors.toList()); + Optional minDateOptional = reportedDates.stream().min(Date::compareTo); + Optional maxDateOptional = reportedDates.stream().max(Date::compareTo); + + if (!minDateOptional.isPresent() || !maxDateOptional.isPresent()) { + return; + } + + Date minDate = minDateOptional.get(); + Date maxDate = maxDateOptional.get(); + + List dates; + String strDateFormat = ""; + switch (periodType) { + case DAILY: + dates = DateHelper.listDaysBetween(minDate, maxDate); + strDateFormat = "MMM dd, yyyy"; + break; + case WEEKLY: + dates = DateHelper.listWeeksBetween(minDate, maxDate); + strDateFormat = "'" + I18nProperties.getString(Strings.weekShort) + "' w, yyyy"; + break; + case MONTHLY: + dates = DateHelper.listMonthsBetween(minDate, maxDate); + strDateFormat = "MMM yyyy"; + break; + case YEARLY: + dates = DateHelper.listYearsBetween(minDate, maxDate); + strDateFormat = "yyyy"; + break; + default: + dates = Collections.emptyList(); + } + + SimpleDateFormat dateFormat = new SimpleDateFormat(strDateFormat); + + cmbPeriodFilter.setItems(dates); + for (Date date : dates) { + String caption = DateHelper.formatLocalDate(date, dateFormat); + cmbPeriodFilter.setCaption(caption); + if (reloadFlag != PeriodFilterReloadFlag.RELOAD_AND_CLEAR_VALUE && caption.equals(cachedDateValue)) + cmbPeriodFilter.setValue(date); + } + + if (reloadFlag == PeriodFilterReloadFlag.RELOAD_AND_CLEAR_VALUE) + cmbPeriodFilter.setValue(cmbPeriodFilter.getData()); } @Override @@ -245,7 +925,7 @@ protected void addLayerOptions(VerticalLayout layersLayout) { caseClassificationOptions.setValue(caseClassificationOption); caseClassificationOptions.addValueChangeListener(event -> { caseClassificationOption = (MapCaseClassificationOption) event.getProperty().getValue(); - refreshMap(true); + refreshMapDashboard(true); }); // Optiongroup to select what property the coordinates should be based on @@ -255,7 +935,7 @@ protected void addLayerOptions(VerticalLayout layersLayout) { mapCaseDisplayModeSelect.setValue(mapCaseDisplayMode); mapCaseDisplayModeSelect.addValueChangeListener(event -> { mapCaseDisplayMode = (MapCaseDisplayMode) event.getProperty().getValue(); - refreshMap(true); + refreshMapDashboard(true); }); if (UiUtil.permitted(UserRight.CASE_VIEW)) { @@ -353,7 +1033,7 @@ protected void addLayerOptions(VerticalLayout layersLayout) { regionMapVisualizationSelect.setValue(caseMeasure); regionMapVisualizationSelect.addValueChangeListener(event -> { caseMeasure = (CaseMeasure) event.getProperty().getValue(); - refreshMap(true); + refreshMapDashboard(true); }); HorizontalLayout showRegionsLayout = new HorizontalLayout(); @@ -368,7 +1048,7 @@ protected void addLayerOptions(VerticalLayout layersLayout) { showRegions = (boolean) e.getProperty().getValue(); regionMapVisualizationSelect.setEnabled(showRegions); regionMapVisualizationSelect.setValue(caseMeasure); - refreshMap(true); + refreshMapDashboard(true); }); showRegionsLayout.addComponent(showRegionsCheckBox); @@ -390,7 +1070,7 @@ protected void addLayerOptions(VerticalLayout layersLayout) { hideOtherCountriesCheckBox.setValue(hideOtherCountries); hideOtherCountriesCheckBox.addValueChangeListener(e -> { hideOtherCountries = (boolean) e.getProperty().getValue(); - refreshMap(true); + refreshMapDashboard(true); }); CssStyles.style(hideOtherCountriesCheckBox, CssStyles.VSPACE_3); layersLayout.addComponent(hideOtherCountriesCheckBox); @@ -401,11 +1081,54 @@ protected void addLayerOptions(VerticalLayout layersLayout) { showCurrentEpiSituationCB.setValue(false); showCurrentEpiSituationCB.addValueChangeListener(e -> { showCurrentEpiSituation = (boolean) e.getProperty().getValue(); - refreshMap(true); + refreshMapDashboard(true); }); layersLayout.addComponent(showCurrentEpiSituationCB); } + private HorizontalLayout createMapHeader() { + + HorizontalLayout mapHeaderLayout = new HorizontalLayout(); + mapHeaderLayout.setWidth(100, Unit.PERCENTAGE); + mapHeaderLayout.setSpacing(true); + CssStyles.style(mapHeaderLayout, CssStyles.VSPACE_4); + + Label mapLabel = new Label(); + if (dashboardDataProvider.getDashboardType() == DashboardType.SURVEILLANCE) { + mapLabel.setValue(I18nProperties.getString(Strings.headingCaseStatusMap)); + CssStyles.style(mapLabel, CssStyles.H2, CssStyles.VSPACE_4, CssStyles.VSPACE_TOP_NONE); + } + else if (dashboardDataProvider.getDashboardType() == DashboardType.DISEASE) { + mapLabel.setValue(I18nProperties.getCaption(Captions.diseaseDetailMap)); + CssStyles.style(mapLabel, CssStyles.H4, CssStyles.VSPACE_4, CssStyles.VSPACE_NONE); + } + else { + mapLabel.setValue(I18nProperties.getString(Strings.headingContactMap)); + CssStyles.style(mapLabel, CssStyles.H2, CssStyles.VSPACE_4, CssStyles.VSPACE_TOP_NONE); + } + mapLabel.setSizeUndefined(); + + mapHeaderLayout.addComponent(mapLabel); + mapHeaderLayout.setComponentAlignment(mapLabel, Alignment.BOTTOM_LEFT); + mapHeaderLayout.setExpandRatio(mapLabel, 1); + + // "Expand" and "Collapse" buttons + Button expandMapButton = + ButtonHelper.createIconButtonWithCaption("expandMap", "", VaadinIcons.EXPAND, null, CssStyles.BUTTON_SUBTLE, CssStyles.VSPACE_NONE); + Button collapseMapButton = + ButtonHelper.createIconButtonWithCaption("collapseMap", "", VaadinIcons.COMPRESS, null, CssStyles.BUTTON_SUBTLE, CssStyles.VSPACE_NONE); + + expandMapButton.addClickListener(e -> { + externalExpandListener.accept(true); + mapHeaderLayout.removeComponent(expandMapButton); + mapHeaderLayout.addComponent(collapseMapButton); + mapHeaderLayout.setComponentAlignment(collapseMapButton, Alignment.MIDDLE_RIGHT); + }); + + + return mapHeaderLayout; + } + @Override protected List getLegendComponents() { List legendComponents = new ArrayList<>(); @@ -1040,4 +1763,55 @@ protected void onMarkerClicked(String groupId, int markerIndex) { break; } } + + private void refreshMapDashboard(boolean forced) { + + clearRegionShapes(); + clearCaseMarkers(); + clearContactMarkers(); + clearEventMarkers(); + LeafletMapUtil.clearOtherCountriesOverlay(map); + + if (hideOtherCountries) { + LeafletMapUtil.addOtherCountriesOverlay(map); + } + + Date fromDate = dashboardDataProvider.getFromDate(); + Date toDate = dashboardDataProvider.getToDate(); + + if (showRegions) { + showRegionsShapes(caseMeasure, fromDate, toDate, dashboardDataProvider.getDisease()); + } + + int maxDisplayCount = FacadeProvider.getConfigFacade().getDashboardMapMarkerLimit(); + + Long count = 0L; + if (!forced && maxDisplayCount >= 0) { + count = getMarkerCount(fromDate, toDate, maxDisplayCount); + } + if(dashboardDataProvider.getDashboardType().equals(DashboardType.DISEASE)) { + if (!forced && maxDisplayCount >= 0 && count > maxDisplayCount) { + makeMapOverlayVisible(maxDisplayCount); + } else { + makeMapOverlayInvisible(); + + loadMapData(fromDate, toDate); + } + } + } + + public void refreshMapDashboard() { + refreshMapDashboard(false); + } + + private void makeMapOverlayVisible(int maxCount) { + overlayBackground.setVisible(true); + overlayLayout.setVisible(true); + overlayMessageLabel.setValue(String.format(I18nProperties.getString(Strings.warningDashboardMapTooManyMarkers), maxCount)); + } + + private void makeMapOverlayInvisible() { + overlayBackground.setVisible(false); + overlayLayout.setVisible(false); + } } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/map/MapCasePeriodOption.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/map/MapCasePeriodOption.java new file mode 100644 index 00000000000..67dc651a250 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/map/MapCasePeriodOption.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * 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.dashboard.map; + +import de.symeda.sormas.api.i18n.I18nProperties; + +public enum MapCasePeriodOption { + + NEW_CASES, + CASES_INCIDENCE; + + @Override + public String toString() { + + return I18nProperties.getEnumCaption(this); + } +} \ No newline at end of file diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/map/MapPeriodType.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/map/MapPeriodType.java new file mode 100644 index 00000000000..0bee9500172 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/map/MapPeriodType.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * 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.dashboard.map; + +import de.symeda.sormas.api.i18n.I18nProperties; + +public enum MapPeriodType { + + DAILY, + WEEKLY, + MONTHLY, + YEARLY; + + public String toString() { + + return I18nProperties.getEnumCaption(this); + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/surveillance/SurveillanceOverviewLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/surveillance/SurveillanceOverviewLayout.java index 3120c18d05e..e1d4e300e85 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/surveillance/SurveillanceOverviewLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/surveillance/SurveillanceOverviewLayout.java @@ -69,7 +69,7 @@ public SurveillanceOverviewLayout(DashboardDataProvider dashboardDataProvider) { } private void addDiseaseBurdenView() { - diseaseOverviewComponent = new DiseaseOverviewComponent(); + diseaseOverviewComponent = new DiseaseOverviewComponent(dashboardDataProvider); addComponent(diseaseOverviewComponent, BURDEN_LOC); if (UiUtil.hasRegionJurisdictionLevel()) diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/surveillance/components/disease/DiseaseOverviewComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/surveillance/components/disease/DiseaseOverviewComponent.java index ee9d4ee5cb7..660cc43d894 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/surveillance/components/disease/DiseaseOverviewComponent.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/surveillance/components/disease/DiseaseOverviewComponent.java @@ -15,6 +15,7 @@ import de.symeda.sormas.ui.dashboard.surveillance.components.disease.tile.DiseaseTileViewLayout; import de.symeda.sormas.ui.utils.ButtonHelper; import de.symeda.sormas.ui.utils.CssStyles; +import de.symeda.sormas.ui.dashboard.DashboardDataProvider; public class DiseaseOverviewComponent extends HorizontalLayout { @@ -25,12 +26,13 @@ public class DiseaseOverviewComponent extends HorizontalLayout { private final Button showTableViewButton; - public DiseaseOverviewComponent() { + public DiseaseOverviewComponent(DashboardDataProvider dashboardDataProvider) { + setWidth(100, Sizeable.Unit.PERCENTAGE); setMargin(false); diseaseBurdenComponent = new DiseaseBurdenComponent(); - diseaseTileViewLayout = new DiseaseTileViewLayout(); + diseaseTileViewLayout = new DiseaseTileViewLayout(dashboardDataProvider); addComponent(diseaseTileViewLayout); setExpandRatio(diseaseTileViewLayout, 1); diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/surveillance/components/disease/tile/DiseaseTileComponent.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/surveillance/components/disease/tile/DiseaseTileComponent.java index 111d45b244f..4ed64a57276 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/surveillance/components/disease/tile/DiseaseTileComponent.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/surveillance/components/disease/tile/DiseaseTileComponent.java @@ -30,12 +30,20 @@ import de.symeda.sormas.api.i18n.I18nProperties; import de.symeda.sormas.api.i18n.Strings; import de.symeda.sormas.ui.utils.CssStyles; +import com.vaadin.ui.Button; +import de.symeda.sormas.ui.ControllerProvider; +import de.symeda.sormas.ui.dashboard.DashboardDataProvider; +import de.symeda.sormas.ui.utils.ButtonHelper; +import com.vaadin.ui.themes.ValoTheme; +import de.symeda.sormas.api.feature.FeatureType; +import de.symeda.sormas.api.user.UserRight; +import static de.symeda.sormas.ui.UiUtil.permitted; public class DiseaseTileComponent extends VerticalLayout { private static final long serialVersionUID = 6582975657305031105L; - public DiseaseTileComponent(DiseaseBurdenDto diseaseBurden) { + public DiseaseTileComponent(DiseaseBurdenDto diseaseBurden, DashboardDataProvider dashboardDataProvider) { setMargin(false); setSpacing(false); @@ -44,7 +52,8 @@ public DiseaseTileComponent(DiseaseBurdenDto diseaseBurden) { diseaseBurden.getCaseCount(), diseaseBurden.getPreviousCaseCount(), diseaseBurden.getOutbreakDistrictCount() > 0); - addStatsLayout(diseaseBurden.getCaseDeathCount(), diseaseBurden.getEventCount(), diseaseBurden.getLastReportedDistrictName()); + addStatsLayout(diseaseBurden, dashboardDataProvider); + } private void addTopLayout(Disease disease, Long casesCount, Long previousCasesCount, boolean isOutbreak) { @@ -147,7 +156,13 @@ private void addTopLayout(Disease disease, Long casesCount, Long previousCasesCo addComponent(layout); } - private void addStatsLayout(Long fatalities, Long events, String district) { + private void addStatsLayout(DiseaseBurdenDto diseaseBurden, DashboardDataProvider dashboardDataProvider) { + + Long fatalities = diseaseBurden.getCaseDeathCount(); + Long events = diseaseBurden.getEventCount(); + String district = diseaseBurden.getLastReportedDistrictName(); + Disease disease = diseaseBurden.getDisease(); + VerticalLayout layout = new VerticalLayout(); layout.setWidth(100, Unit.PERCENTAGE); layout.setMargin(false); @@ -160,11 +175,35 @@ private void addStatsLayout(Long fatalities, Long events, String district) { .build(); lastReportItem.addStyleName(CssStyles.VSPACE_TOP_4); layout.addComponent(lastReportItem); - layout.addComponent(new StatsItem.Builder(Captions.dashboardFatalities, fatalities).critical(fatalities > 0).build()); + + StatsItem fatality = new StatsItem.Builder(Captions.dashboardFatalities, fatalities).critical(fatalities > 0).build(); + if (permitted(FeatureType.DISEASE_DETAILS , UserRight.DISEASE_DETAILS_VIEW) ) { + fatality.addStyleName(CssStyles.HSPACE_LEFT_5); + } + + layout.addComponent(fatality); + StatsItem noOfEventsItem = new StatsItem.Builder(Captions.DiseaseBurden_eventCount, events).build(); noOfEventsItem.addStyleName(CssStyles.VSPACE_4); layout.addComponent(noOfEventsItem); + if (permitted(FeatureType.DISEASE_DETAILS , UserRight.DISEASE_DETAILS_VIEW) ) { + Button component = addDiseaseButton(disease, dashboardDataProvider); + layout.addComponent(component); + } + addComponent(layout); } + + private Button addDiseaseButton(Disease diseaseName, DashboardDataProvider dashboardDataProvider) { + + Button diseaseDetailButton = ButtonHelper.createIconButton(null, VaadinIcons.ELLIPSIS_DOTS_H, null, + ValoTheme.BUTTON_BORDERLESS, CssStyles.VSPACE_TOP_NONE, CssStyles.VSPACE_4); + diseaseDetailButton.setVisible(true); + + diseaseDetailButton.addClickListener( + click -> ControllerProvider.getDashboardController().navigateToDisease(diseaseName, dashboardDataProvider)); + + return diseaseDetailButton; + } } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/surveillance/components/disease/tile/DiseaseTileViewLayout.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/surveillance/components/disease/tile/DiseaseTileViewLayout.java index edd2c665f18..ccf650162aa 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/surveillance/components/disease/tile/DiseaseTileViewLayout.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/surveillance/components/disease/tile/DiseaseTileViewLayout.java @@ -23,11 +23,19 @@ import com.vaadin.ui.CssLayout; import de.symeda.sormas.api.disease.DiseaseBurdenDto; +import de.symeda.sormas.ui.dashboard.DashboardDataProvider; public class DiseaseTileViewLayout extends CssLayout { + private final DashboardDataProvider dashboardDataProvider; + private static final long serialVersionUID = 6582975657305031105L; + public DiseaseTileViewLayout(DashboardDataProvider dashboardDataProvider) { + + this.dashboardDataProvider = dashboardDataProvider; + } + @Override protected String getCss(Component c) { return "margin-left: 18px; margin-bottom: 18px;"; @@ -37,7 +45,7 @@ public void refresh(List diseasesBurden) { this.removeAllComponents(); for (DiseaseBurdenDto diseaseBurden : diseasesBurden) { - DiseaseTileComponent tile = new DiseaseTileComponent(diseaseBurden); + DiseaseTileComponent tile = new DiseaseTileComponent(diseaseBurden,this.dashboardDataProvider); tile.setWidth(230, Unit.PIXELS); addComponent(tile); } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/surveillance/components/disease/tile/RegionalDiseaseBurdenGrid.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/surveillance/components/disease/tile/RegionalDiseaseBurdenGrid.java new file mode 100644 index 00000000000..40b9615ce85 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/dashboard/surveillance/components/disease/tile/RegionalDiseaseBurdenGrid.java @@ -0,0 +1,280 @@ +/******************************************************************************* + * 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.dashboard.surveillance.components.disease.tile; + +import com.vaadin.v7.data.util.BeanItemContainer; +import com.vaadin.v7.data.util.GeneratedPropertyContainer; +import com.vaadin.v7.shared.ui.grid.ColumnResizeMode; +import com.vaadin.v7.ui.Grid; +import com.vaadin.v7.ui.renderers.HtmlRenderer; +import de.symeda.sormas.api.FacadeProvider; +import de.symeda.sormas.api.disease.DiseaseBurdenDto; +import de.symeda.sormas.api.i18n.Captions; +import de.symeda.sormas.api.i18n.I18nProperties; +import de.symeda.sormas.api.infrastructure.district.DistrictReferenceDto; +import de.symeda.sormas.api.infrastructure.region.RegionDto; +import de.symeda.sormas.ui.dashboard.DashboardDataProvider; + +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; + +public class RegionalDiseaseBurdenGrid extends Grid { + + private final DashboardDataProvider dashboardDataProvider; + private final List regionDtoList; + + + Grid.Column regionDistrictColumn; + Grid.Column totalColumn; + Grid.Column totalCountColumn; + Grid.Column activeCaseColumn; + Grid.Column activeCaseCountColumn; + Grid.Column recoveredCasesColumn; + Grid.Column recoveredCasesCountColumn; + Grid.Column deathColumn; + Grid.Column deathCountColumn; + Grid.Column otherColumn; + Grid.Column otherCountColumn; + + private final DecimalFormat decimalFormat; + + public RegionalDiseaseBurdenGrid(DashboardDataProvider dashboardDataProvider) { + + this.dashboardDataProvider = dashboardDataProvider; + regionDtoList = FacadeProvider.getRegionFacade().getAllActiveRegions(); + + + setCaption(I18nProperties.getCaption(Captions.dashboardRegionalDiseaseBurden)); + + decimalFormat = new DecimalFormat("0.00"); + setColumnReorderingAllowed(true); + setWidthFull(); + setWidth(1000, Unit.PIXELS); + + setColumns( + DiseaseBurdenDto.CASES_REGION, + DiseaseBurdenDto.CASES_COUNT_TOTAL, + DiseaseBurdenDto.CASES_TOTAL, + DiseaseBurdenDto.ACTIVE_COUNT_CASE, + DiseaseBurdenDto.ACTIVE_CASE, + DiseaseBurdenDto.RECOVERED_COUNT_CASES, + DiseaseBurdenDto.RECOVERED_CASES, + DiseaseBurdenDto.DEATH_COUNT, + DiseaseBurdenDto.DEATH, + DiseaseBurdenDto.OTHER_COUNT, + DiseaseBurdenDto.OTHER + ); + + regionDistrictColumn = getColumn(DiseaseBurdenDto.CASES_REGION); + + totalCountColumn = getColumn(DiseaseBurdenDto.CASES_COUNT_TOTAL); + totalColumn = getColumn(DiseaseBurdenDto.CASES_TOTAL); + + activeCaseCountColumn = getColumn(DiseaseBurdenDto.ACTIVE_COUNT_CASE); + activeCaseColumn = getColumn(DiseaseBurdenDto.ACTIVE_CASE); + + recoveredCasesCountColumn = getColumn(DiseaseBurdenDto.RECOVERED_COUNT_CASES); + recoveredCasesColumn = getColumn(DiseaseBurdenDto.RECOVERED_CASES); + + deathCountColumn = getColumn(DiseaseBurdenDto.DEATH_COUNT); + deathColumn = getColumn(DiseaseBurdenDto.DEATH); + + otherCountColumn = getColumn(DiseaseBurdenDto.OTHER_COUNT); + otherColumn = getColumn(DiseaseBurdenDto.OTHER); + + } + + public void refresh(){ + + setColumnResizeMode(ColumnResizeMode.ANIMATED); + setSelectionMode(Grid.SelectionMode.NONE); + + reload(); + } + + public void reload() { + + List diseaseBurdenDtoList = new ArrayList<>(); + + Long casePercental = dashboardDataProvider.getDiseaseBurdenDetail().getCaseCount(); + + regionDistrictColumn.setWidth(100); + regionDistrictColumn.setHeaderCaption("REGION NAME"); + + totalCountColumn.setRenderer(new HtmlRenderer()).setWidth(100); + totalColumn.setRenderer(new HtmlRenderer()).setWidth(100); + totalColumn.setHeaderCaption("TOTAL %"); + + activeCaseCountColumn.setRenderer(new HtmlRenderer()).setWidth(100); + activeCaseColumn.setRenderer(new HtmlRenderer()).setWidth(100); + activeCaseColumn.setHeaderCaption("ACTIVE CASES %"); + + recoveredCasesCountColumn.setRenderer(new HtmlRenderer()).setWidth(100); + recoveredCasesCountColumn.setHeaderCaption("RECOVER COUNT"); + recoveredCasesColumn.setRenderer(new HtmlRenderer()).setWidth(100); + recoveredCasesColumn.setHeaderCaption("RECOVER CASES %"); + + + deathCountColumn.setRenderer(new HtmlRenderer()).setWidth(100); + deathColumn.setRenderer(new HtmlRenderer()).setWidth(100); + deathColumn.setHeaderCaption("DEATH CASES %"); + otherCountColumn.setRenderer(new HtmlRenderer()).setWidth(100); + otherColumn.setRenderer(new HtmlRenderer()).setWidth(100); + otherColumn.setHeaderCaption("OTHER CASES %"); + + if(dashboardDataProvider.getRegion()!=null) { + + regionDistrictColumn.setHeaderCaption("DISTRICT NAME"); + setCaption(I18nProperties.getCaption(Captions.dashboardDistrictDiseaseBurden)); + + String regionUuid=dashboardDataProvider.getRegion().getUuid(); + + List districtDtoList = FacadeProvider.getDistrictFacade().getAllActiveByRegion(regionUuid); + + for (DistrictReferenceDto districtDto : districtDtoList){ + + DiseaseBurdenDto diseaseBurdenDto = FacadeProvider.getDashboardFacade().getDiseaseGridForDashboard( + null, + districtDto, + dashboardDataProvider.getDisease(), + dashboardDataProvider.getFromDate(), + dashboardDataProvider.getToDate(), + dashboardDataProvider.getPreviousFromDate(), + dashboardDataProvider.getPreviousToDate(), + dashboardDataProvider.getNewCaseDateType(), + dashboardDataProvider.getCaseClassification() + ); + + + String total = diseaseBurdenDto.getTotal(); + String activeCases = diseaseBurdenDto.getActiveCases(); + String recovered = diseaseBurdenDto.getRecovered(); + String deaths = diseaseBurdenDto.getDeaths(); + String other = diseaseBurdenDto.getOther(); + RegionDto regionDto = new RegionDto(); + regionDto.setName(districtDto.getCaption()); + + diseaseBurdenDto.setRegion(regionDto); + diseaseBurdenDto.setTotal(makeDIvs(Long.parseLong(total), casePercental, "#5a95f4bf","#2f7df9")); + diseaseBurdenDto.setTotalCount(makeDIvsCount(total)); + diseaseBurdenDto.setActiveCases(makeDIvs(Long.parseLong(activeCases), casePercental, "#feba0199", "#dfa507")); + diseaseBurdenDto.setActiveCount(makeDIvsCount(activeCases)); + diseaseBurdenDto.setRecovered(makeDIvs(Long.parseLong(recovered), casePercental, "#00e0a19c", "#038d66")); + diseaseBurdenDto.setRecoveredCount(makeDIvsCount(recovered)); + diseaseBurdenDto.setDeaths(makeDIvs(Long.parseLong(deaths), casePercental,"#FFAEAE", "#FF4040")); + diseaseBurdenDto.setDeathsCount(makeDIvsCount(deaths)); + diseaseBurdenDto.setOther(makeDIvs(Long.parseLong(other), casePercental,"#bf8678ba", "#91675d")); + diseaseBurdenDto.setOtherCount(makeDIvsCount(other)); + diseaseBurdenDtoList.add(diseaseBurdenDto); + } + }else { + + for (RegionDto regionDto : regionDtoList){ + DiseaseBurdenDto diseaseBurdenDto = FacadeProvider.getDashboardFacade().getDiseaseGridForDashboard( + regionDto.toReference(), + null, + dashboardDataProvider.getDisease(), + dashboardDataProvider.getFromDate(), + dashboardDataProvider.getToDate(), + dashboardDataProvider.getPreviousFromDate(), + dashboardDataProvider.getPreviousToDate(), + dashboardDataProvider.getNewCaseDateType(), + dashboardDataProvider.getCaseClassification() + ); + + String total = diseaseBurdenDto.getTotal(); + String activeCases = diseaseBurdenDto.getActiveCases(); + String recovered = diseaseBurdenDto.getRecovered(); + String deaths = diseaseBurdenDto.getDeaths(); + String other = diseaseBurdenDto.getOther(); + + diseaseBurdenDto.setTotal(makeDIvs(Long.parseLong(total), casePercental, "#5a95f4bf","#2f7df9")); + diseaseBurdenDto.setTotalCount(makeDIvsCount(total)); + diseaseBurdenDto.setActiveCases(makeDIvs(Long.parseLong(activeCases), casePercental, "#feba0199", "#dfa507")); + diseaseBurdenDto.setActiveCount(makeDIvsCount(activeCases)); + diseaseBurdenDto.setRecovered(makeDIvs(Long.parseLong(recovered), casePercental, "#00e0a19c", "#038d66")); + diseaseBurdenDto.setRecoveredCount(makeDIvsCount(recovered)); + diseaseBurdenDto.setDeaths(makeDIvs(Long.parseLong(deaths), casePercental,"#FFAEAE", "#FF4040")); + diseaseBurdenDto.setDeathsCount(makeDIvsCount(deaths)); + diseaseBurdenDto.setOther(makeDIvs(Long.parseLong(other), casePercental,"#bf8678ba", "#91675d")); + diseaseBurdenDto.setOtherCount(makeDIvsCount(other)); + diseaseBurdenDtoList.add(diseaseBurdenDto); + } + } + + BeanItemContainer container = new BeanItemContainer<>(DiseaseBurdenDto.class, diseaseBurdenDtoList); + GeneratedPropertyContainer generatedContainer = new GeneratedPropertyContainer(container); + setContainerDataSource(generatedContainer); + } + + public String makeDIvs(long number, long total, String lightColor, String deepColor) { + + String endDiv = "
"; + String divWithStyleAttr="
" + + decimalFormat.format(regionalTotal)+"% "+endDiv + + element( div, style, null) + endDiv; + } + + return divWithStyleAttr+mainStyle+"'>
" + + decimalFormat.format(regionalTotal)+"% "+endDiv + + element(div , style, null) + endDiv; + + } + + public String makeDIvsCount(String num) { + + String endDiv = "
"; + String divWithStyleAttr="
" + + regionalTotal+endDiv; + } + + public String element(String type, String style, String content) { + + StringBuilder sb = new StringBuilder(); + sb.append("<").append(type); + if (style != null) { + sb.append(" style='").append(style).append("'"); + } + sb.append(">"); + + if (content != null) + sb.append(content); + + sb.append(""); + return sb.toString(); + + } +} \ No newline at end of file diff --git a/sormas-ui/src/main/webapp/VAADIN/themes/sormas/views/disease.scss b/sormas-ui/src/main/webapp/VAADIN/themes/sormas/views/disease.scss index bd44207e044..ce984e2a2e6 100644 --- a/sormas-ui/src/main/webapp/VAADIN/themes/sormas/views/disease.scss +++ b/sormas-ui/src/main/webapp/VAADIN/themes/sormas/views/disease.scss @@ -190,4 +190,59 @@ filled: #b0796b; } } + .disease-detail-card-display-top{ + display: inline-grid; + overflow: hidden; + } + + .disease-detail-card-display-down{ + display: block; + } + + .old-disease-detail-outbreak-display{ + text-align: center; + font-size: smaller; + font-weight: 700; + width: 100px; + background:#DE5555; + -ms-transform: rotate(-135deg); + -o-transform: rotate(-135deg); + -webkit-transform:rotate(45deg); + color: white; + transform-origin: bottom; + float: right; + margin-right: -57px; + margin-top: -7px; + } + + .map-border-layout .leaflet-container{ + border: 2px solid #CDD8EC; + } + + .container-death-cfr{ + width: 100%; + } + + .container-float-left{ + float: left; + display: inline-flex; + } + .container-float-right{ + float: right; + } + + .disease-detail-outbreak-display{ + text-align: center; + font-size: smaller; + font-weight: 700; + width: 100px; + background:#e33232; + -ms-transform: rotate(-135deg); + -o-transform: rotate(-135deg); + -webkit-transform:rotate(45deg); + color: white; + float: right; + margin-right: -57px; + margin-top: -7px; + } } \ No newline at end of file diff --git a/sormas-ui/src/main/webapp/WEB-INF/glassfish-web.xml b/sormas-ui/src/main/webapp/WEB-INF/glassfish-web.xml index cc6f9e6ccf8..2f1cb43dd34 100644 --- a/sormas-ui/src/main/webapp/WEB-INF/glassfish-web.xml +++ b/sormas-ui/src/main/webapp/WEB-INF/glassfish-web.xml @@ -622,7 +622,12 @@ DASHBOARD_ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_VIEW - DASHBOARD_ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_VIEW + DASHBOARD_ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_VI+EW + + + + DISEASE_DETAILS_VIEW + DISEASE_DETAILS_VIEW diff --git a/sormas-ui/src/main/webapp/WEB-INF/web.xml b/sormas-ui/src/main/webapp/WEB-INF/web.xml index b62eb6a327a..5916fc2f1d9 100644 --- a/sormas-ui/src/main/webapp/WEB-INF/web.xml +++ b/sormas-ui/src/main/webapp/WEB-INF/web.xml @@ -513,6 +513,10 @@ DASHBOARD_ADVERSE_EVENTS_FOLLOWING_IMMUNIZATION_VIEW + + DISEASE_DETAILS_VIEW + + CASE_CLINICIAN_VIEW diff --git a/sormas-ui/src/test/java/de/symeda/sormas/ui/dashboard/disease/DiseaseDashboardServiceTest.java b/sormas-ui/src/test/java/de/symeda/sormas/ui/dashboard/disease/DiseaseDashboardServiceTest.java new file mode 100644 index 00000000000..79b82438c56 --- /dev/null +++ b/sormas-ui/src/test/java/de/symeda/sormas/ui/dashboard/disease/DiseaseDashboardServiceTest.java @@ -0,0 +1,163 @@ +/******************************************************************************* + * 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.dashboard.disease; + +import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.caze.CaseClassification; +import de.symeda.sormas.api.caze.NewCaseDateType; +import de.symeda.sormas.api.dashboard.DashboardCriteria; +import de.symeda.sormas.api.disease.DiseaseBurdenDto; +import de.symeda.sormas.api.event.EventCriteria; +import de.symeda.sormas.api.infrastructure.district.DistrictReferenceDto; +import de.symeda.sormas.api.infrastructure.region.RegionDto; +import de.symeda.sormas.api.infrastructure.region.RegionReferenceDto; +import de.symeda.sormas.api.outbreak.OutbreakCriteria; +import de.symeda.sormas.api.utils.criteria.CriteriaDateType; +import de.symeda.sormas.backend.dashboard.DashboardFacadeEjb; +import de.symeda.sormas.backend.dashboard.DashboardService; +import de.symeda.sormas.backend.disease.DiseaseConfigurationFacadeEjb; +import de.symeda.sormas.backend.event.EventFacadeEjb.EventFacadeEjbLocal; +import de.symeda.sormas.backend.feature.FeatureConfigurationFacadeEjb; +import de.symeda.sormas.backend.infrastructure.district.District; +import de.symeda.sormas.backend.infrastructure.region.RegionFacadeEjb.RegionFacadeEjbLocal; +import de.symeda.sormas.backend.outbreak.OutbreakFacadeEjb.OutbreakFacadeEjbLocal; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +public class DiseaseDashboardServiceTest { + + @Mock + private EventFacadeEjbLocal eventFacade; + + @Mock + private OutbreakFacadeEjbLocal outbreakFacade; + + @Mock + private DiseaseConfigurationFacadeEjb.DiseaseConfigurationFacadeEjbLocal diseaseConfigurationFacade; + + @Mock + private RegionFacadeEjbLocal regionFacade; + + @Mock + private DashboardService dashboardService; + + @Mock + private FeatureConfigurationFacadeEjb.FeatureConfigurationFacadeEjbLocal featureConfigurationFacade; + + @InjectMocks + private DashboardFacadeEjb dashboardfacadeEjb; + + @BeforeEach + public void setUp() { + + MockitoAnnotations.openMocks(this); + } + + @Test + public void testGetDiseaseForDashboard() { + + RegionReferenceDto region = new RegionReferenceDto(); + DistrictReferenceDto district = new DistrictReferenceDto(); + Disease disease = Disease.CORONAVIRUS; + Date fromDate = new Date(); + Date toDate = new Date(); + Date previousFrom = new Date(); + Date previousTo = new Date(); + CriteriaDateType newCaseDateType = NewCaseDateType.MOST_RELEVANT; + CaseClassification caseClassification = null; + + // Mocking responses for dashboardService, eventFacade, outbreakFacade, etc. + Map newCases = new HashMap<>(); + newCases.put(disease, 8L); + when(dashboardService.getCaseCountByDisease(any(DashboardCriteria.class))).thenReturn(newCases); + + Map events = new HashMap<>(); + events.put(disease, 5L); + when(eventFacade.getEventCountByDisease(any(EventCriteria.class))).thenReturn(events); + + Map outbreakDistricts = new HashMap<>(); + outbreakDistricts.put(disease, new District()); + when(outbreakFacade.getOutbreakDistrictNameByDisease(any(OutbreakCriteria.class))).thenReturn(outbreakDistricts); + + Map lastReportedDistricts = new HashMap<>(); + lastReportedDistricts.put(disease, new District()); + when(dashboardService.getLastReportedDistrictByDisease(any(DashboardCriteria.class))).thenReturn(lastReportedDistricts); + + Map caseFatalities = new HashMap<>(); + caseFatalities.put(disease, 1L); + when(dashboardService.getDeathCountByDisease(any(DashboardCriteria.class))).thenReturn(caseFatalities); + + Map previousCases = new HashMap<>(); + previousCases.put(disease, 8L); + when(dashboardService.getCaseCountByDisease(any(DashboardCriteria.class))).thenReturn(previousCases); + + // Invoke the method under test + DiseaseBurdenDto result = dashboardfacadeEjb.getDiseaseForDashboard( + region, district, disease, fromDate, toDate, previousFrom, previousTo, newCaseDateType, caseClassification); + + // Assert expected results + assertEquals(8L, result.getCaseCount()); + assertEquals(8L, result.getPreviousCaseCount()); + assertEquals(5L, result.getEventCount()); + assertNull(result.getOutbreakDistrict()); + assertEquals(1L, result.getCaseDeathCount()); + assertEquals(Disease.CORONAVIRUS, result.getDisease()); + } + + @Test + public void testGetDiseaseGridForDashboard() { + + RegionReferenceDto region = new RegionReferenceDto(); + DistrictReferenceDto district = new DistrictReferenceDto(); + Disease disease = Disease.CORONAVIRUS; + Date fromDate = new Date(); + Date toDate = new Date(); + Date previousFrom = new Date(); + Date previousTo = new Date(); + CriteriaDateType newCaseDateType = NewCaseDateType.MOST_RELEVANT; + CaseClassification caseClassification = null; + + RegionDto regionDto = new RegionDto(); + Map allCasesFetched = new HashMap<>(); + allCasesFetched.put(disease, 15L); + Map caseFatalities = new HashMap<>(); + caseFatalities.put(disease, 2L); + + when(regionFacade.getByUuid(region.getUuid())).thenReturn(regionDto); + when(dashboardService.getCaseCountByDisease(any(DashboardCriteria.class))) + .thenReturn(allCasesFetched); + when(dashboardService.getDeathCountByDisease(any(DashboardCriteria.class))) + .thenReturn(caseFatalities); + + DiseaseBurdenDto result = dashboardfacadeEjb.getDiseaseGridForDashboard( + region, district, disease, fromDate, toDate, previousFrom, previousTo, newCaseDateType, caseClassification); + + assertEquals("15", result.getTotal()); + assertEquals("2", result.getDeaths()); + } +} \ No newline at end of file