415 lines
22 KiB
HTML
415 lines
22 KiB
HTML
{% extends "base.html" %}
|
|
{% load static i18n %}
|
|
|
|
{% block title %}{% translate "Modifier le bâtiment" %} {{ building.code }} | StreetUp{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container py-4">
|
|
<!-- Breadcrumb & Title -->
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<div>
|
|
<nav aria-label="breadcrumb">
|
|
<ol class="breadcrumb mb-1">
|
|
<li class="breadcrumb-item"><a href="{% url 'assets:buildings_index' %}">{% translate "Bâtiments publics" %}</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'assets:publicbuilding_detail' building.pk %}">{{ building.code }}</a></li>
|
|
<li class="breadcrumb-item active" aria-current="page">{% translate "Modifier" %}</li>
|
|
</ol>
|
|
</nav>
|
|
<h1 class="h3 mb-0">
|
|
<i class="bi bi-pencil-square text-primary me-2"></i>{% translate "Modifier le bâtiment" %} {{ building.code }}
|
|
</h1>
|
|
</div>
|
|
<div>
|
|
<a href="{% url 'assets:publicbuilding_detail' building.pk %}" class="btn btn-outline-secondary">
|
|
<i class="bi bi-arrow-left me-1"></i>{% translate "Annuler" %}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<form method="post" action="" class="needs-validation" novalidate>
|
|
{% csrf_token %}
|
|
|
|
{% if form.errors %}
|
|
<div class="alert alert-danger alert-dismissible fade show mb-4" role="alert">
|
|
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
|
<strong>{% translate "Certains champs contiennent des erreurs." %}</strong> {% translate "Veuillez vérifier les informations ci-dessous." %}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Card 1: Identités & Localisation -->
|
|
<div class="card shadow-sm mb-4">
|
|
<div class="card-header bg-light fw-bold">
|
|
<i class="bi bi-info-circle me-2"></i>{% translate "Informations générales" %}
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row g-3">
|
|
<div class="col-md-4">
|
|
<label for="{{ form.code.id_for_label }}" class="form-label fw-semibold">{% translate "Code du bâtiment" %} <span class="text-danger">*</span></label>
|
|
{{ form.code }}
|
|
{% if form.code.errors %}<div class="text-danger small mt-1">{{ form.code.errors.0 }}</div>{% endif %}
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label for="{{ form.name_fr.id_for_label }}" class="form-label fw-semibold">{% translate "Nom (FR)" %}</label>
|
|
{{ form.name_fr }}
|
|
{% if form.name_fr.errors %}<div class="text-danger small mt-1">{{ form.name_fr.errors.0 }}</div>{% endif %}
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label for="{{ form.name_nl.id_for_label }}" class="form-label fw-semibold">{% translate "Nom (NL)" %}</label>
|
|
{{ form.name_nl }}
|
|
{% if form.name_nl.errors %}<div class="text-danger small mt-1">{{ form.name_nl.errors.0 }}</div>{% endif %}
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label for="{{ form.location_type.id_for_label }}" class="form-label fw-semibold">{% translate "Type de localisation" %}</label>
|
|
{{ form.location_type }}
|
|
{% if form.location_type.errors %}<div class="text-danger small mt-1">{{ form.location_type.errors.0 }}</div>{% endif %}
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label for="{{ form.parent_location.id_for_label }}" class="form-label fw-semibold">{% translate "Localisation parente" %}</label>
|
|
{{ form.parent_location }}
|
|
{% if form.parent_location.errors %}<div class="text-danger small mt-1">{{ form.parent_location.errors.0 }}</div>{% endif %}
|
|
</div>
|
|
<div class="col-md-4 d-flex align-items-end mb-2">
|
|
<div class="form-check">
|
|
{{ form.is_main_location }}
|
|
<label for="{{ form.is_main_location.id_for_label }}" class="form-check-label fw-semibold">{% translate "Localisation principale" %}</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-12">
|
|
<label for="{{ form.address.id_for_label }}" class="form-label fw-semibold">{% translate "Adresse complète" %}</label>
|
|
{{ form.address }}
|
|
{% if form.address.errors %}<div class="text-danger small mt-1">{{ form.address.errors.0 }}</div>{% endif %}
|
|
</div>
|
|
<div class="col-md-12">
|
|
<label for="{{ form.description.id_for_label }}" class="form-label fw-semibold">{% translate "Description" %}</label>
|
|
{{ form.description }}
|
|
{% if form.description.errors %}<div class="text-danger small mt-1">{{ form.description.errors.0 }}</div>{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Card 2: Propriété & Gestion -->
|
|
<div class="card shadow-sm mb-4">
|
|
<div class="card-header bg-light fw-bold">
|
|
<i class="bi bi-person-badge me-2"></i>{% translate "Propriété & Gestion" %}
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row g-3">
|
|
<div class="col-md-4">
|
|
<label for="{{ form.owner.id_for_label }}" class="form-label fw-semibold">{% translate "Propriétaire" %}</label>
|
|
{{ form.owner }}
|
|
{% if form.owner.errors %}<div class="text-danger small mt-1">{{ form.owner.errors.0 }}</div>{% endif %}
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label for="{{ form.manager.id_for_label }}" class="form-label fw-semibold">{% translate "Gestionnaire référent" %}</label>
|
|
{{ form.manager }}
|
|
{% if form.manager.errors %}<div class="text-danger small mt-1">{{ form.manager.errors.0 }}</div>{% endif %}
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label for="{{ form.occupant_entity.id_for_label }}" class="form-label fw-semibold">{% translate "Direction / Service occupant" %}</label>
|
|
{{ form.occupant_entity }}
|
|
{% if form.occupant_entity.errors %}<div class="text-danger small mt-1">{{ form.occupant_entity.errors.0 }}</div>{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Card 3: Usage & Caractéristiques physiques -->
|
|
<div class="card shadow-sm mb-4">
|
|
<div class="card-header bg-light fw-bold">
|
|
<i class="bi bi-building-gear me-2"></i>{% translate "Usage & Caractéristiques physiques" %}
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row g-3">
|
|
<div class="col-md-6">
|
|
<label for="{{ form.usage_type.id_for_label }}" class="form-label fw-semibold">{% translate "Type d'usage principal" %}</label>
|
|
{{ form.usage_type }}
|
|
{% if form.usage_type.errors %}<div class="text-danger small mt-1">{{ form.usage_type.errors.0 }}</div>{% endif %}
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label for="{{ form.building_status.id_for_label }}" class="form-label fw-semibold">{% translate "Statut d'occupation" %}</label>
|
|
{{ form.building_status }}
|
|
{% if form.building_status.errors %}<div class="text-danger small mt-1">{{ form.building_status.errors.0 }}</div>{% endif %}
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label for="{{ form.construction_year.id_for_label }}" class="form-label fw-semibold">{% translate "Année de construction" %}</label>
|
|
{{ form.construction_year }}
|
|
{% if form.construction_year.errors %}<div class="text-danger small mt-1">{{ form.construction_year.errors.0 }}</div>{% endif %}
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label for="{{ form.floors_count.id_for_label }}" class="form-label fw-semibold">{% translate "Nombre d'étages hors-sol" %}</label>
|
|
{{ form.floors_count }}
|
|
{% if form.floors_count.errors %}<div class="text-danger small mt-1">{{ form.floors_count.errors.0 }}</div>{% endif %}
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label for="{{ form.basement_count.id_for_label }}" class="form-label fw-semibold">{% translate "Nombre de niveaux en sous-sol" %}</label>
|
|
{{ form.basement_count }}
|
|
{% if form.basement_count.errors %}<div class="text-danger small mt-1">{{ form.basement_count.errors.0 }}</div>{% endif %}
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label for="{{ form.total_surface_m2.id_for_label }}" class="form-label fw-semibold">{% translate "Surface brute totale (m²)" %}</label>
|
|
{{ form.total_surface_m2 }}
|
|
{% if form.total_surface_m2.errors %}<div class="text-danger small mt-1">{{ form.total_surface_m2.errors.0 }}</div>{% endif %}
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label for="{{ form.usable_surface_m2.id_for_label }}" class="form-label fw-semibold">{% translate "Surface utile (m²)" %}</label>
|
|
{{ form.usable_surface_m2 }}
|
|
{% if form.usable_surface_m2.errors %}<div class="text-danger small mt-1">{{ form.usable_surface_m2.errors.0 }}</div>{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Card 4: Réglementation & Accueil du Public (ERP / PMR) -->
|
|
<div class="card shadow-sm mb-4">
|
|
<div class="card-header bg-light fw-bold">
|
|
<i class="bi bi-shield-check me-2"></i>{% translate "Accueil & Sécurité du Public (ERP / PMR)" %}
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row g-3">
|
|
<div class="col-md-6 d-flex align-items-center">
|
|
<div class="form-check mt-3">
|
|
{{ form.is_erp }}
|
|
<label for="{{ form.is_erp.id_for_label }}" class="form-check-label fw-semibold">
|
|
{% translate "Bâtiment recevant du public (ERP / PMR)" %}
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6 d-flex align-items-center">
|
|
<div class="form-check mt-3">
|
|
{{ form.pmr_accessible }}
|
|
<label for="{{ form.pmr_accessible.id_for_label }}" class="form-check-label fw-semibold">
|
|
{% translate "Accessible PMR" %}
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label for="{{ form.erp_category.id_for_label }}" class="form-label fw-semibold">{% translate "Catégorie d'accueil / ERP" %}</label>
|
|
{{ form.erp_category }}
|
|
{% if form.erp_category.errors %}<div class="text-danger small mt-1">{{ form.erp_category.errors.0 }}</div>{% endif %}
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label for="{{ form.erp_type.id_for_label }}" class="form-label fw-semibold">{% translate "Type d'établissement / ERP" %}</label>
|
|
{{ form.erp_type }}
|
|
{% if form.erp_type.errors %}<div class="text-danger small mt-1">{{ form.erp_type.errors.0 }}</div>{% endif %}
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label for="{{ form.max_capacity.id_for_label }}" class="form-label fw-semibold">{% translate "Jauge maximale d'accueil du public" %}</label>
|
|
{{ form.max_capacity }}
|
|
{% if form.max_capacity.errors %}<div class="text-danger small mt-1">{{ form.max_capacity.errors.0 }}</div>{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Card 5: Diagnostics & Énergie -->
|
|
<div class="card shadow-sm mb-4">
|
|
<div class="card-header bg-light fw-bold">
|
|
<i class="bi bi-lightning-charge me-2"></i>{% translate "Diagnostics & Énergie" %}
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row g-3">
|
|
<div class="col-md-6">
|
|
<label for="{{ form.dpe_rating.id_for_label }}" class="form-label fw-semibold">{% translate "Performance Énergétique (PEB / DPE / EPC)" %}</label>
|
|
{{ form.dpe_rating }}
|
|
{% if form.dpe_rating.errors %}<div class="text-danger small mt-1">{{ form.dpe_rating.errors.0 }}</div>{% endif %}
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label for="{{ form.ges_rating.id_for_label }}" class="form-label fw-semibold">{% translate "Émissions de Carbone & GES" %}</label>
|
|
{{ form.ges_rating }}
|
|
{% if form.ges_rating.errors %}<div class="text-danger small mt-1">{{ form.ges_rating.errors.0 }}</div>{% endif %}
|
|
</div>
|
|
<div class="col-md-6 d-flex align-items-center">
|
|
<div class="form-check mt-2">
|
|
{{ form.has_asbestos_diag }}
|
|
<label for="{{ form.has_asbestos_diag.id_for_label }}" class="form-check-label fw-semibold">
|
|
{% translate "Diagnostic Amiante réalisé" %}
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6 d-flex align-items-center">
|
|
<div class="form-check mt-2">
|
|
{{ form.has_lead_diag }}
|
|
<label for="{{ form.has_lead_diag.id_for_label }}" class="form-check-label fw-semibold">
|
|
{% translate "Diagnostic Plomb réalisé" %}
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Card 6: Géolocalisation (Carte & Coordonnées) -->
|
|
<div class="card shadow-sm mb-4">
|
|
<div class="card-header bg-light fw-bold">
|
|
<i class="bi bi-geo-alt me-2"></i>{% translate "Coordonnées géographiques (GPS WGS84)" %}
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="text-muted small mb-3">
|
|
<i class="bi bi-info-circle me-1"></i>{% translate "Cliquez sur la carte ou déplacez le marqueur pour ajuster la position du bâtiment. Vous pouvez également rechercher une adresse ou basculer en vue orthophoto." %}
|
|
</p>
|
|
<div id="edit-building-map" style="height: 400px; width: 100%; border-radius: 8px; border: 1px solid #dee2e6;" class="mb-3"></div>
|
|
<div class="row g-3">
|
|
<div class="col-md-6">
|
|
<label for="{{ form.lat.id_for_label }}" class="form-label fw-semibold">{% translate "Latitude" %}</label>
|
|
{{ form.lat }}
|
|
{% if form.lat.errors %}<div class="text-danger small mt-1">{{ form.lat.errors.0 }}</div>{% endif %}
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label for="{{ form.lon.id_for_label }}" class="form-label fw-semibold">{% translate "Longitude" %}</label>
|
|
{{ form.lon }}
|
|
{% if form.lon.errors %}<div class="text-danger small mt-1">{{ form.lon.errors.0 }}</div>{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Form Submit & Cancel -->
|
|
<div class="d-flex justify-content-end gap-2 mb-5">
|
|
<a href="{% url 'assets:publicbuilding_detail' building.pk %}" class="btn btn-secondary px-4">
|
|
<i class="bi bi-x-lg me-1"></i>{% translate "Annuler" %}
|
|
</a>
|
|
<button type="submit" class="btn btn-primary px-4">
|
|
<i class="bi bi-check-circle me-1"></i>{% translate "Enregistrer les modifications" %}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
{% endblock content %}
|
|
|
|
{% block script %}
|
|
<script src="{% static 'common/layer-switcher-control.js' %}"></script>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const latInput = document.getElementById("{{ form.lat.id_for_label }}");
|
|
const lonInput = document.getElementById("{{ form.lon.id_for_label }}");
|
|
|
|
let initLat = parseFloat("{{ center_lat|default_if_none:'' }}") || parseFloat(latInput.value) || 50.8477;
|
|
let initLon = parseFloat("{{ center_lon|default_if_none:'' }}") || parseFloat(lonInput.value) || 4.3572;
|
|
let hasCoords = Boolean("{{ center_lat|default_if_none:'' }}" || (latInput && latInput.value));
|
|
|
|
const map = new maplibregl.Map({
|
|
container: 'edit-building-map',
|
|
style: window.MAP_STYLE_LIGHT || 'https://tiles.openfreemap.org/styles/positron',
|
|
center: [initLon, initLat],
|
|
zoom: hasCoords ? 17 : 12,
|
|
});
|
|
|
|
// 1. Navigation & Geolocate Controls
|
|
map.addControl(new maplibregl.NavigationControl(), 'bottom-right');
|
|
map.addControl(new maplibregl.GeolocateControl({ positionOptions: { enableHighAccuracy: true }, trackUserLocation: true }), 'bottom-right');
|
|
|
|
// 2. Custom Address Search Control
|
|
if (typeof AddressSearchControl !== 'undefined') {
|
|
const searchCtrl = new AddressSearchControl({
|
|
autocompleteUrl: "{% url 'common:autocomplete_street' %}"
|
|
});
|
|
map.addControl(searchCtrl, 'top-left');
|
|
|
|
searchCtrl.addEventListener('address-selected', function(e) {
|
|
if (e.detail && e.detail.lon && e.detail.lat) {
|
|
const lon = parseFloat(e.detail.lon);
|
|
const lat = parseFloat(e.detail.lat);
|
|
map.flyTo({ center: [lon, lat], zoom: 18 });
|
|
setMarkerPosition(lat, lon);
|
|
}
|
|
});
|
|
}
|
|
|
|
// 3. Orthophotos / Satellite LayerSwitcherControl
|
|
if (typeof LayerSwitcherControl !== 'undefined') {
|
|
const layerSwitcher = new LayerSwitcherControl({
|
|
layers: [
|
|
{
|
|
id: 'ortho',
|
|
name: 'Orthophotos',
|
|
icon: 'satellite',
|
|
group: 'ortho',
|
|
layer: {
|
|
type: 'raster',
|
|
paint: { 'raster-opacity': 1 }
|
|
},
|
|
sublayers: [
|
|
{
|
|
id: 'urbis',
|
|
name: window.MAP_ORTHO_WMS_NAME || 'Urbis (Bruxelles)',
|
|
default: true,
|
|
source: {
|
|
type: 'raster',
|
|
tiles: [
|
|
window.MAP_ORTHO_WMS_URL || 'https://geoservices-urbis.irisnet.be/geoserver/urbisgrid/wms?service=WMS&version=1.3.0&request=GetMap&layers=Ortho&styles=&format=image/png&transparent=true&width=256&height=256&crs=EPSG:3857&bbox={bbox-epsg-3857}'
|
|
],
|
|
tileSize: 256,
|
|
attribution: window.MAP_ORTHO_WMS_ATTRIBUTION || '© CIRB / Urbis'
|
|
}
|
|
},
|
|
{
|
|
id: 'vlaanderen',
|
|
name: 'Vlaanderen 2023',
|
|
source: {
|
|
type: 'raster',
|
|
tiles: ['https://geo.api.vlaanderen.be/OMW/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=omwrgb23vl&STYLE=&FORMAT=image/png&TILEMATRIXSET=GoogleMapsVL&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}'],
|
|
tileSize: 256,
|
|
attribution: '© Digitaal Vlaanderen - Orthofoto 2023'
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
});
|
|
map.addControl(layerSwitcher, 'top-right');
|
|
}
|
|
|
|
// 4. Marker logic & Form synchronization
|
|
let marker = null;
|
|
if (hasCoords) {
|
|
marker = new maplibregl.Marker({ draggable: true, color: '#2B579A' })
|
|
.setLngLat([initLon, initLat])
|
|
.addTo(map);
|
|
|
|
marker.on('dragend', function() {
|
|
const lngLat = marker.getLngLat();
|
|
updateInputValues(lngLat.lat, lngLat.lng);
|
|
});
|
|
}
|
|
|
|
map.on('click', function(e) {
|
|
setMarkerPosition(e.lngLat.lat, e.lngLat.lng);
|
|
});
|
|
|
|
function setMarkerPosition(lat, lon) {
|
|
if (!marker) {
|
|
marker = new maplibregl.Marker({ draggable: true, color: '#2B579A' })
|
|
.setLngLat([lon, lat])
|
|
.addTo(map);
|
|
|
|
marker.on('dragend', function() {
|
|
const lngLat = marker.getLngLat();
|
|
updateInputValues(lngLat.lat, lngLat.lng);
|
|
});
|
|
} else {
|
|
marker.setLngLat([lon, lat]);
|
|
}
|
|
updateInputValues(lat, lon);
|
|
}
|
|
|
|
function updateInputValues(lat, lon) {
|
|
if (latInput) latInput.value = parseFloat(lat).toFixed(7);
|
|
if (lonInput) lonInput.value = parseFloat(lon).toFixed(7);
|
|
}
|
|
|
|
function syncMarkerFromInputs() {
|
|
const lat = parseFloat(latInput.value);
|
|
const lon = parseFloat(lonInput.value);
|
|
if (!isNaN(lat) && !isNaN(lon)) {
|
|
setMarkerPosition(lat, lon);
|
|
map.panTo([lon, lat]);
|
|
}
|
|
}
|
|
|
|
if (latInput) latInput.addEventListener('change', syncMarkerFromInputs);
|
|
if (lonInput) lonInput.addEventListener('change', syncMarkerFromInputs);
|
|
});
|
|
</script>
|
|
{% endblock script %}
|