Skip to content

Commit f4137b6

Browse files
Merge pull request dimagi#1015 from dimagi/hy/select-work
2 parents e5142bf + f012df1 commit f4137b6

File tree

5 files changed

+121
-21
lines changed

5 files changed

+121
-21
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from commcare_connect.microplanning.models import WorkAreaStatus
2+
3+
WORK_AREA_STATUS_COLORS = {
4+
WorkAreaStatus.NOT_STARTED: "bg-gray-200 text-gray-700",
5+
WorkAreaStatus.UNASSIGNED: "bg-gray-200 text-gray-700",
6+
WorkAreaStatus.NOT_VISITED: "bg-gray-200 text-gray-700",
7+
WorkAreaStatus.VISITED: "bg-yellow-200 text-yellow-900",
8+
WorkAreaStatus.REQUEST_FOR_INACCESSIBLE: "bg-yellow-200 text-yellow-900",
9+
WorkAreaStatus.EXPECTED_VISIT_REACHED: "bg-green-200 text-green-900",
10+
WorkAreaStatus.INACCESSIBLE: "bg-gray-500 text-white",
11+
WorkAreaStatus.EXCLUDED: "bg-gray-500 text-white",
12+
}

commcare_connect/microplanning/views.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424

2525
from commcare_connect.flags.decorators import require_flag_for_opp
2626
from commcare_connect.flags.flag_names import MICROPLANNING
27-
from commcare_connect.microplanning.models import WorkArea, WorkAreaGroup
27+
from commcare_connect.microplanning.const import WORK_AREA_STATUS_COLORS
28+
from commcare_connect.microplanning.models import WorkArea, WorkAreaGroup, WorkAreaStatus
2829
from commcare_connect.organization.decorators import opportunity_required, org_admin_required
2930
from commcare_connect.utils.file import get_file_extension
3031

@@ -56,6 +57,15 @@ def microplanning_home(request, *args, **kwargs):
5657
"opp_id": opportunity.opportunity_id,
5758
},
5859
)
60+
61+
status_meta = {
62+
status.value: {
63+
"label": status.label,
64+
"class": WORK_AREA_STATUS_COLORS.get(status),
65+
}
66+
for status in WorkAreaStatus
67+
}
68+
5969
return render(
6070
request,
6171
template_name="microplanning/home.html",
@@ -68,6 +78,7 @@ def microplanning_home(request, *args, **kwargs):
6878
"metrics": get_metrics_for_microplanning(opportunity),
6979
"tiles_url": tiles_url,
7080
"groups_url": groups_url,
81+
"status_meta": status_meta,
7182
"workarea_min_zoom": WORKAREA_MIN_ZOOM,
7283
},
7384
)
@@ -162,11 +173,7 @@ def import_status(request, org_slug, opp_id):
162173

163174
class WorkAreaVectorLayer(VectorLayer):
164175
id = "workareas"
165-
tile_fields = (
166-
"id",
167-
"status",
168-
"group_id",
169-
)
176+
tile_fields = ("id", "status", "building_count", "expected_visit_count", "group_id", "group_name", "assignee_name")
170177
geom_field = "boundary"
171178
min_zoom = WORKAREA_MIN_ZOOM
172179

@@ -177,6 +184,8 @@ def __init__(self, *args, opp_id=None, **kwargs):
177184
def get_queryset(self):
178185
return WorkArea.objects.filter(opportunity_id=self.opp_id).annotate(
179186
group_id=F("work_area_group__id"),
187+
group_name=F("work_area_group__name"),
188+
assignee_name=F("work_area_group__assigned_user__user__name"),
180189
)
181190

182191

commcare_connect/templates/microplanning/home.html

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,59 @@ <h2 class="text-lg font-semibold mb-2">{% translate "Settings" %}</h2>
8282
<!-- Sidebar content goes here -->
8383
</aside>
8484

85-
<aside id="right-sidebar" class="bg-white shadow-md p-4 overflow-auto xl:flex-1 min-h-0">
86-
<h2 class="text-lg font-semibold mb-2">{% translate "Modify Work Area" %}</h2>
87-
<!-- Outer sidebar content goes here -->
88-
</aside>
89-
</div>
90-
</div>
85+
<aside id="lower-sidebar" class="bg-white shadow-md p-4 xl:flex-1 min-h-0">
86+
{{ status_meta|json_script:"status-meta-data" }}
87+
<h2 class="text-lg font-semibold mb-2">{% translate "Select Work Area" %}</h2>
88+
<template x-if="!selectedFeature">
89+
<div class="flex flex-col items-center justify-center h-full text-center text-gray-400 gap-2 py-8">
90+
<i class="fa-regular fa-map text-3xl"></i>
91+
<p class="text-sm">{% translate "Click a work area on the map to view details." %}</p>
92+
</div>
93+
</template>
94+
95+
<!-- Selected work area detail panel -->
96+
<template x-if="selectedFeature">
97+
<div>
98+
<dl class="space-y-2">
99+
<div class="grid grid-cols-2 items-center gap-2">
100+
<dt class="text-sm text-gray-500">{% translate "Assignee" %}</dt>
101+
<dd class="text-sm font-medium text-right border border-gray-200 rounded-lg px-3 py-2 bg-gray-50 truncate"
102+
x-text="selectedFeature.assignee_name ?? '—'"></dd>
103+
104+
<dt class="text-sm text-gray-500">{% translate "Work Area Group" %}</dt>
105+
<dd class="text-sm font-medium text-right border border-gray-200 rounded-lg px-3 py-2 bg-gray-50 truncate"
106+
x-text="selectedFeature.group_name ?? '—'"></dd>
107+
108+
<dt class="text-sm text-gray-500">{% translate "Expected Visit Count" %}</dt>
109+
<dd class="text-sm font-medium text-right border border-gray-200 rounded-lg px-3 py-2 bg-gray-50"
110+
x-text="selectedFeature.expected_visit_count ?? '—'"></dd>
111+
112+
<dt class="text-sm text-gray-500">{% translate "Number of Buildings" %}</dt>
113+
<dd class="text-sm font-medium text-right border border-gray-200 rounded-lg px-3 py-2 bg-gray-50"
114+
x-text="selectedFeature.building_count ?? '—'"></dd>
115+
</div>
116+
<div class="flex items-center gap-4"
117+
x-data="{statusMeta: JSON.parse(document.getElementById('status-meta-data').textContent)}">
118+
<dt class="text-sm text-gray-500 shrink-0">{% translate "Status" %}</dt>
119+
<dd class="flex-1 min-w-0">
120+
<span class="block w-full text-center text-sm font-semibold rounded-lg px-3 py-2"
121+
:class="statusMeta[selectedFeature.status]?.class"
122+
x-text="statusMeta[selectedFeature.status]?.label">
123+
</span>
124+
</dd>
125+
</div>
126+
</dl>
127+
<div class="mt-4 flex justify-end">
128+
<button class="button button-md button-outline-rounded">
129+
<i class="fa-regular fa-pen-to-square"></i>
130+
{% translate "Modify Work Area" %}
131+
</button>
132+
</div>
133+
</div>
134+
</template>
135+
</aside>
136+
</div>
137+
</div>
91138
</div>
92139
{% include "microplanning/map_handler.html" %}
93140
{% endblock %}

commcare_connect/templates/microplanning/map_handler.html

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
document.addEventListener("alpine:init", () => {
33
Alpine.data("mapController", () => ({
44
map: null,
5-
selectedAreas: new Set(),
5+
selectedFeature: null,
66

77
init() {
88
this.$nextTick(() => {
@@ -99,7 +99,7 @@
9999
'source-layer': 'workareas',
100100
minzoom: 8,
101101
paint: {
102-
'line-color': '#ffff00',
102+
'line-color': '#00ffff',
103103
'line-width': 3,
104104
'line-opacity': [
105105
'case',
@@ -159,19 +159,42 @@
159159
.catch(err => console.error(err));
160160

161161

162-
// Toggles selection state on clicked workareas.
162+
// When the user clicks on the map, Mapbox returns the geographic object
163+
// under the cursor as a "feature". A feature represents a single polygon
164+
// (in our case a workarea) along with its attributes in `properties`.
165+
// `feature` → the raw Mapbox object returned from the click event.
166+
// `selectedFeature` → our Alpine state that stores the currently selected workarea.
167+
// We store its properties and ID so we can toggle the highlight and access its data.
163168
this.map.on('click', 'workareas-fill', (e) => {
164169
const feature = e.features?.[0];
165170
if (!feature || feature.id == null) return;
166171
const id = feature.id;
167-
if (this.selectedAreas.has(id)) {
168-
this.selectedAreas.delete(id);
169-
} else {
170-
this.selectedAreas.add(id);
172+
if (this.selectedFeature) {
173+
this.map.setFeatureState(
174+
{
175+
source: 'workareas',
176+
sourceLayer: 'workareas',
177+
id: this.selectedFeature._id
178+
},
179+
{ selected: false }
180+
);
171181
}
182+
if (this.selectedFeature && this.selectedFeature._id === id) {
183+
this.selectedFeature = null;
184+
return;
185+
}
186+
this.selectedFeature = {
187+
...e.features[0].properties,
188+
_id: id
189+
};
190+
172191
this.map.setFeatureState(
173-
{ source: 'workareas', sourceLayer: 'workareas', id },
174-
{ selected: this.selectedAreas.has(id) }
192+
{
193+
source: 'workareas',
194+
sourceLayer: 'workareas',
195+
id
196+
},
197+
{ selected: true }
175198
);
176199
});
177200

tailwind/safelists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,12 @@ gap-16
1717

1818
font-bold
1919
border-red-500
20+
21+
bg-gray-200
22+
text-gray-700
23+
bg-yellow-200
24+
text-yellow-900
25+
bg-green-200
26+
text-green-900
27+
bg-gray-500
28+
text-white

0 commit comments

Comments
 (0)