diff --git a/commcare_connect/microplanning/tests/test_views.py b/commcare_connect/microplanning/tests/test_views.py index a5c213509..6d7b6bda4 100644 --- a/commcare_connect/microplanning/tests/test_views.py +++ b/commcare_connect/microplanning/tests/test_views.py @@ -483,6 +483,7 @@ def test_queryset_annotates_location_point(self, opportunity, visit_data): assert round(visit["location_point"].x, 1) == 77.1 assert round(visit["location_point"].y, 1) == 28.6 + assert visit["work_area_id"] == visit_data.work_area.id def test_queryset_only_includes_visits_for_opportunity(self, opportunity, visit_data): other_opp = OpportunityFactory() diff --git a/commcare_connect/microplanning/views.py b/commcare_connect/microplanning/views.py index 60cdeac74..7da66a616 100644 --- a/commcare_connect/microplanning/views.py +++ b/commcare_connect/microplanning/views.py @@ -308,7 +308,7 @@ def get_layers(self): class UserVisitVectorLayer(VectorLayer): id = "user-visits" - tile_fields = () + tile_fields = ("work_area_id",) geom_field = "location_point" min_zoom = WORKAREA_MIN_ZOOM @@ -342,7 +342,7 @@ def get_queryset(self): output_field=PointField(srid=4326), ) ) - .values("location_point") + .values("location_point", "work_area_id") ) diff --git a/commcare_connect/templates/microplanning/map_handler.html b/commcare_connect/templates/microplanning/map_handler.html index a1968306a..bef394854 100644 --- a/commcare_connect/templates/microplanning/map_handler.html +++ b/commcare_connect/templates/microplanning/map_handler.html @@ -5,6 +5,7 @@ Alpine.data("mapController", () => ({ map: null, selectedFeature: null, + highlightedWorkAreaId: null, showWorkAreaEditModal: false, showVisits: false, filterQuery: '', @@ -35,6 +36,84 @@ .then(() => this.showWorkAreaEditModal = true) }, + updateHighlightedWorkAreaId() { + this.highlightedWorkAreaId = (this.assignmentMode || !this.selectedFeature) + ? null + : this.selectedFeature._id; + }, + + buildVisitCirclePaint() { + const hasSelection = this.highlightedWorkAreaId !== null; + return { + 'circle-radius': [ + 'interpolate', ['linear'], ['zoom'], + 8, 2, + 14, 5, + ], + 'circle-color': '#e74c3c', + 'circle-opacity': hasSelection ? 0.2 : 0.7, + 'circle-stroke-width': 1, + 'circle-stroke-color': '#ffffff', + 'circle-stroke-opacity': 0.5, + }; + }, + + addVisitHighlightLayer() { + if (!this.map || this.map.getLayer('user-visits-highlight')) return; + this.map.addLayer({ + id: 'user-visits-highlight', + type: 'symbol', + source: 'user-visits', + 'source-layer': 'user-visits', + layout: { + 'text-field': '◆', + 'text-size': [ + 'interpolate', ['linear'], ['zoom'], + 8, 13, + 14, 22, + ], + 'text-font': ['DIN Pro Medium', 'Arial Unicode MS Regular'], + 'text-allow-overlap': true, + 'text-ignore-placement': true, + 'visibility': 'none', + }, + paint: { + 'text-color': '#22c55e', + 'text-halo-color': '#000000', + 'text-halo-width': 2.5, + }, + }); + }, + + refreshVisitStyling() { + if (!this.map) return; + + if (this.map.getLayer('user-visits-circle')) { + this.map.setPaintProperty( + 'user-visits-circle', + 'circle-opacity', + this.highlightedWorkAreaId !== null ? 0.2 : 0.7, + ); + } + + if (this.map.getLayer('user-visits-highlight')) { + const id = this.highlightedWorkAreaId; + if (id === null) { + this.map.setLayoutProperty('user-visits-highlight', 'visibility', 'none'); + } else { + this.map.setFilter( + 'user-visits-highlight', + ['==', ['get', 'work_area_id'], id], + ); + this.map.setLayoutProperty( + 'user-visits-highlight', + 'visibility', + this.showVisits ? 'visible' : 'none', + ); + } + } + }, + getDownloadUrl() { const baseUrl = '{{ download_url|escapejs }}'; return this.filterQuery ? `${baseUrl}?${this.filterQuery}` : baseUrl; @@ -339,6 +418,9 @@ this.initializeMap(); }); + this.$watch('selectedFeature', () => this.updateHighlightedWorkAreaId()); + this.$watch('highlightedWorkAreaId', () => this.refreshVisitStyling()); + if (this.assignmentMode) { const assigneesEl = document.getElementById('assignees-data'); if (assigneesEl) this.assignees = JSON.parse(assigneesEl.textContent); @@ -566,26 +648,20 @@ type: 'circle', source: 'user-visits', 'source-layer': 'user-visits', - paint: { - 'circle-radius': [ - 'interpolate', ['linear'], ['zoom'], - 8, 2, - 14, 5 - ], - 'circle-color': '#e74c3c', - 'circle-opacity': 0.7, - 'circle-stroke-width': 1, - 'circle-stroke-color': '#ffffff', - 'circle-stroke-opacity': 0.5, - } + paint: this.buildVisitCirclePaint(), }); + this.addVisitHighlightLayer(); visitsLayerAdded = true; + this.refreshVisitStyling(); } else if (visitsLayerAdded) { - this.map.setLayoutProperty( - 'user-visits-circle', - 'visibility', - visible ? 'visible' : 'none' - ); + this.map.setLayoutProperty('user-visits-circle', 'visibility', visible ? 'visible' : 'none'); + if (this.map.getLayer('user-visits-highlight')) { + if (!visible) { + this.map.setLayoutProperty('user-visits-highlight', 'visibility', 'none'); + } else { + this.refreshVisitStyling(); + } + } } });