feat: implement asset preselection in quick inspection map with coordinate and filter auto-focus
This commit is contained in:
parent
ccc465bf5d
commit
79673367f0
4 changed files with 185 additions and 6 deletions
|
|
@ -22,9 +22,19 @@
|
||||||
if (mapContainer.dataset.mapInitialized === 'true') return;
|
if (mapContainer.dataset.mapInitialized === 'true') return;
|
||||||
mapContainer.dataset.mapInitialized = 'true';
|
mapContainer.dataset.mapInitialized = 'true';
|
||||||
|
|
||||||
// Centrage et style depuis les settings globaux (window.MAP_STYLE_LIGHT / window.DEFAULT_MAP_CENTER)
|
// Centrage et style depuis les settings globaux ou pré-sélection d'asset
|
||||||
const defaultCenter = window.DEFAULT_MAP_CENTER || [4.3517, 50.8503];
|
let defaultCenter = window.DEFAULT_MAP_CENTER || [4.3517, 50.8503];
|
||||||
const defaultZoom = window.DEFAULT_MAP_ZOOM || 15;
|
let defaultZoom = window.DEFAULT_MAP_ZOOM || 15;
|
||||||
|
|
||||||
|
if (window.PRESELECT_LON !== null && window.PRESELECT_LAT !== null && typeof window.PRESELECT_LON !== 'undefined' && typeof window.PRESELECT_LAT !== 'undefined') {
|
||||||
|
const preLon = parseFloat(window.PRESELECT_LON);
|
||||||
|
const preLat = parseFloat(window.PRESELECT_LAT);
|
||||||
|
if (!isNaN(preLon) && !isNaN(preLat) && preLon >= -180 && preLon <= 180 && preLat >= -90 && preLat <= 90 && !(preLon === 0 && preLat === 0)) {
|
||||||
|
defaultCenter = [preLon, preLat];
|
||||||
|
defaultZoom = 19;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const styleUrl = window.MAP_STYLE_LIGHT || 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json';
|
const styleUrl = window.MAP_STYLE_LIGHT || 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json';
|
||||||
|
|
||||||
map = new maplibregl.Map({
|
map = new maplibregl.Map({
|
||||||
|
|
@ -304,6 +314,34 @@
|
||||||
map.getCanvas().style.cursor = '';
|
map.getCanvas().style.cursor = '';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Prise en compte de la pré-sélection d'un asset (zoom, thématique, catégorie)
|
||||||
|
if (window.PRESELECT_THEMATIC_CODE) {
|
||||||
|
const tileBtn = document.querySelector(`.thematic-tile[data-thematic="${window.PRESELECT_THEMATIC_CODE}"]`);
|
||||||
|
if (tileBtn) {
|
||||||
|
document.querySelectorAll('.thematic-tile').forEach(t => t.classList.remove('active'));
|
||||||
|
tileBtn.classList.add('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.PRESELECT_CATEGORY_ID) {
|
||||||
|
const catSelect = document.getElementById('filter-category-select');
|
||||||
|
if (catSelect) {
|
||||||
|
catSelect.value = String(window.PRESELECT_CATEGORY_ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.PRESELECT_LON !== null && window.PRESELECT_LAT !== null && typeof window.PRESELECT_LON !== 'undefined' && typeof window.PRESELECT_LAT !== 'undefined') {
|
||||||
|
const lon = parseFloat(window.PRESELECT_LON);
|
||||||
|
const lat = parseFloat(window.PRESELECT_LAT);
|
||||||
|
if (!isNaN(lon) && !isNaN(lat) && lon >= -180 && lon <= 180 && lat >= -90 && lat <= 90 && !(lon === 0 && lat === 0)) {
|
||||||
|
map.flyTo({
|
||||||
|
center: [lon, lat],
|
||||||
|
zoom: 19,
|
||||||
|
duration: 1200
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Charger les assets et l'historique d'inspections au premier affichage
|
// Charger les assets et l'historique d'inspections au premier affichage
|
||||||
loadAssets();
|
loadAssets();
|
||||||
loadInspectionsHistory();
|
loadInspectionsHistory();
|
||||||
|
|
@ -383,7 +421,10 @@
|
||||||
gpsBtn.addEventListener('click', locateUser);
|
gpsBtn.addEventListener('click', locateUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
locateUser();
|
const hasPreselect = (window.PRESELECT_LON !== null && window.PRESELECT_LAT !== null && typeof window.PRESELECT_LON !== 'undefined' && typeof window.PRESELECT_LAT !== 'undefined');
|
||||||
|
if (!hasPreselect) {
|
||||||
|
locateUser();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function locateUser() {
|
function locateUser() {
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,18 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h5>
|
</h5>
|
||||||
{% if can_inspect %}
|
{% if can_inspect %}
|
||||||
<a href="{% url 'assets:quick_inspection_map' %}?content_type_id={{ asset_ct_id }}&object_id={{ asset_pk }}" class="btn btn-outline-primary btn-sm">
|
<button type="button"
|
||||||
|
class="btn btn-outline-primary btn-sm btn-trigger-asset-inspection"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#quickInspectionModal"
|
||||||
|
data-content-type-id="{{ asset_ct_id }}"
|
||||||
|
data-object-id="{{ asset_pk }}"
|
||||||
|
data-target-code="{{ asset.code|default:asset.pk }}"
|
||||||
|
data-target-name="{{ asset.name_fr|default:asset.name_nl|default:asset }}"
|
||||||
|
data-target-type="{{ asset.category.name_fr|default:'Asset' }}"
|
||||||
|
data-target-status="{{ asset.get_status_display|default:asset.inspection_status|default:'-' }}">
|
||||||
<i class="bi bi-plus-lg me-1"></i>{% translate "Réaliser une inspection" %}
|
<i class="bi bi-plus-lg me-1"></i>{% translate "Réaliser une inspection" %}
|
||||||
</a>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
|
|
@ -149,3 +158,47 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if can_inspect %}
|
||||||
|
{% include "assets/components/inspection_form_modal.html" %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const triggerBtns = document.querySelectorAll('.btn-trigger-asset-inspection');
|
||||||
|
triggerBtns.forEach(btn => {
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
const ctId = this.getAttribute('data-content-type-id');
|
||||||
|
const objId = this.getAttribute('data-object-id');
|
||||||
|
const code = this.getAttribute('data-target-code');
|
||||||
|
const name = this.getAttribute('data-target-name');
|
||||||
|
const type = this.getAttribute('data-target-type');
|
||||||
|
const status = this.getAttribute('data-target-status');
|
||||||
|
|
||||||
|
const modalCtInput = document.getElementById('insp_content_type_id');
|
||||||
|
const modalObjInput = document.getElementById('insp_object_id');
|
||||||
|
const modalTypeCode = document.getElementById('insp_target_code');
|
||||||
|
const modalTypeName = document.getElementById('insp_target_name');
|
||||||
|
const modalTypeBadge = document.getElementById('insp_target_type');
|
||||||
|
const modalStatusBadge = document.getElementById('insp_target_status');
|
||||||
|
|
||||||
|
if (modalCtInput) modalCtInput.value = ctId;
|
||||||
|
if (modalObjInput) modalObjInput.value = objId;
|
||||||
|
if (modalTypeCode) modalTypeCode.textContent = code;
|
||||||
|
if (modalTypeName) modalTypeName.textContent = name;
|
||||||
|
if (modalTypeBadge) modalTypeBadge.textContent = type;
|
||||||
|
if (modalStatusBadge) modalStatusBadge.textContent = status;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const form = document.getElementById('quickInspectionForm');
|
||||||
|
if (form && !form.dataset.reloadHandlerAttached) {
|
||||||
|
form.dataset.reloadHandlerAttached = 'true';
|
||||||
|
form.addEventListener('submit', function() {
|
||||||
|
setTimeout(function() {
|
||||||
|
window.location.reload();
|
||||||
|
}, 600);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
|
|
||||||
|
|
@ -436,6 +436,12 @@
|
||||||
<script>
|
<script>
|
||||||
window.AUTOCOMPLETE_STREET_URL = "{% url 'common:autocomplete_street' %}";
|
window.AUTOCOMPLETE_STREET_URL = "{% url 'common:autocomplete_street' %}";
|
||||||
window.PARKING_SPOT_CT_ID = {{ parking_spot_ct_id|default:"null" }};
|
window.PARKING_SPOT_CT_ID = {{ parking_spot_ct_id|default:"null" }};
|
||||||
|
window.PRESELECT_CONTENT_TYPE_ID = {{ preselect_ct_id|safe }};
|
||||||
|
window.PRESELECT_OBJECT_ID = {{ preselect_obj_id|safe }};
|
||||||
|
window.PRESELECT_LON = {{ preselect_lon|safe }};
|
||||||
|
window.PRESELECT_LAT = {{ preselect_lat|safe }};
|
||||||
|
window.PRESELECT_THEMATIC_CODE = {{ preselect_thematic_code|safe }};
|
||||||
|
window.PRESELECT_CATEGORY_ID = {{ preselect_category_id|safe }};
|
||||||
</script>
|
</script>
|
||||||
<script src="{% static 'assets/inspection_map.js' %}"></script>
|
<script src="{% static 'assets/inspection_map.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,54 @@ def inspections_list(request):
|
||||||
return render(request, 'assets/inspections_list.html', context)
|
return render(request, 'assets/inspections_list.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_asset_wgs84_lon_lat(obj):
|
||||||
|
if hasattr(obj, 'lon') and hasattr(obj, 'lat') and obj.lon is not None and obj.lat is not None:
|
||||||
|
try:
|
||||||
|
lon_f = float(obj.lon)
|
||||||
|
lat_f = float(obj.lat)
|
||||||
|
if -180 <= lon_f <= 180 and -90 <= lat_f <= 90 and not (lon_f == 0 and lat_f == 0):
|
||||||
|
return lon_f, lat_f
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if hasattr(obj, 'geojson') and obj.geojson:
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
data = json.loads(obj.geojson) if isinstance(obj.geojson, str) else obj.geojson
|
||||||
|
geom_data = data.get('geometry', {}) if data.get('type') == 'Feature' else data
|
||||||
|
g_type = geom_data.get('type')
|
||||||
|
coords = geom_data.get('coordinates')
|
||||||
|
if g_type == 'Point' and coords:
|
||||||
|
return float(coords[0]), float(coords[1])
|
||||||
|
elif g_type == 'Polygon' and coords and len(coords) > 0:
|
||||||
|
ring = coords[0]
|
||||||
|
avg_lon = sum(pt[0] for pt in ring) / len(ring)
|
||||||
|
avg_lat = sum(pt[1] for pt in ring) / len(ring)
|
||||||
|
return avg_lon, avg_lat
|
||||||
|
elif g_type == 'MultiPolygon' and coords and len(coords) > 0:
|
||||||
|
all_pts = [pt for poly in coords for ring in poly for pt in ring]
|
||||||
|
if all_pts:
|
||||||
|
avg_lon = sum(pt[0] for pt in all_pts) / len(all_pts)
|
||||||
|
avg_lat = sum(pt[1] for pt in all_pts) / len(all_pts)
|
||||||
|
return avg_lon, avg_lat
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if hasattr(obj, 'geom') and obj.geom:
|
||||||
|
try:
|
||||||
|
g = obj.geom
|
||||||
|
srid = getattr(g, 'srid', None) or 3812
|
||||||
|
from django.contrib.gis.geos import GEOSGeometry
|
||||||
|
g_copy = GEOSGeometry(g.wkt, srid=srid)
|
||||||
|
g_4326 = g_copy.transform(4326, clone=True)
|
||||||
|
centroid = g_4326.centroid
|
||||||
|
return float(centroid.x), float(centroid.y)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def quick_inspection_map(request):
|
def quick_inspection_map(request):
|
||||||
"""
|
"""
|
||||||
|
|
@ -143,12 +191,43 @@ def quick_inspection_map(request):
|
||||||
from assets.models import ParkingSpot
|
from assets.models import ParkingSpot
|
||||||
parking_spot_ct_id = ContentType.objects.get_for_model(ParkingSpot).id
|
parking_spot_ct_id = ContentType.objects.get_for_model(ParkingSpot).id
|
||||||
|
|
||||||
|
preselect_ct_id = request.GET.get('content_type_id')
|
||||||
|
preselect_obj_id = request.GET.get('object_id')
|
||||||
|
preselect_lon = None
|
||||||
|
preselect_lat = None
|
||||||
|
preselect_thematic_code = None
|
||||||
|
preselect_category_id = None
|
||||||
|
|
||||||
|
if preselect_ct_id and preselect_obj_id:
|
||||||
|
try:
|
||||||
|
ct = ContentType.objects.get(pk=preselect_ct_id)
|
||||||
|
obj = ct.get_object_for_this_type(pk=preselect_obj_id)
|
||||||
|
if obj:
|
||||||
|
from assets.permissions import _get_asset_thematic
|
||||||
|
th = _get_asset_thematic(obj)
|
||||||
|
if th:
|
||||||
|
preselect_thematic_code = th.code
|
||||||
|
if hasattr(obj, 'category') and obj.category:
|
||||||
|
preselect_category_id = obj.category.id
|
||||||
|
|
||||||
|
preselect_lon, preselect_lat = _get_asset_wgs84_lon_lat(obj)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'base_template': base_template,
|
'base_template': base_template,
|
||||||
'is_mobile': is_mobile,
|
'is_mobile': is_mobile,
|
||||||
'thematics': thematics,
|
'thematics': thematics,
|
||||||
'categories': categories,
|
'categories': categories,
|
||||||
'parking_spot_ct_id': parking_spot_ct_id,
|
'parking_spot_ct_id': parking_spot_ct_id,
|
||||||
|
'preselect_ct_id': preselect_ct_id or "null",
|
||||||
|
'preselect_obj_id': preselect_obj_id or "null",
|
||||||
|
'preselect_lon': json.dumps(preselect_lon) if preselect_lon is not None else "null",
|
||||||
|
'preselect_lat': json.dumps(preselect_lat) if preselect_lat is not None else "null",
|
||||||
|
'preselect_thematic_code': json.dumps(preselect_thematic_code) if preselect_thematic_code else "null",
|
||||||
|
'preselect_category_id': preselect_category_id or "null",
|
||||||
}
|
}
|
||||||
return render(request, 'assets/quick_inspection_map.html', context)
|
return render(request, 'assets/quick_inspection_map.html', context)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue