loko/streetup/assets/static/assets/naturerwiz_detail.js
2026-07-22 14:48:40 +02:00

400 lines
14 KiB
JavaScript

const map = new maplibregl.Map({
style: window.MAP_STYLE_LIGHT,
center: [feature_lon, feature_lat],
zoom: 17,
pitch: 45,
bearing: -17.6,
container: 'map',
antialias: true
});
map.on('load', () => {
// 1. Prepare location GeoJSON
let featureGeom = null;
if (feature_geojson) {
try {
featureGeom = typeof feature_geojson === 'string' ? JSON.parse(feature_geojson) : feature_geojson;
} catch (e) {
console.error("Failed to parse feature_geojson", e);
}
}
const location_data = {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: featureGeom,
properties: {
name: 'GIEP Zone'
}
}
]
};
map.addSource('location', {
type: 'geojson',
data: location_data
});
map.addLayer({
'id': 'location',
'type': 'fill',
'source': 'location',
'paint': {
'fill-color': '#0094ff',
'fill-opacity': 0.25,
}
});
map.addLayer({
'id': 'location-outline',
'type': 'line',
'source': 'location',
'paint': {
'line-color': '#0094ff',
'line-width': 2,
'line-dasharray': [2, 2]
}
});
// 2. Prepare assets GeoJSON
let assetsData = { type: 'FeatureCollection', features: [] };
try {
const assetsJson = document.getElementById("assets_geojson_value").textContent;
const parsed = JSON.parse(assetsJson);
assetsData = typeof parsed === 'string' ? JSON.parse(parsed) : parsed;
} catch (e) {
console.error("Failed to parse assets_geojson", e);
}
map.addSource('assets', {
type: 'geojson',
data: assetsData
});
// Polygons fill
map.addLayer({
'id': 'assets-polygons',
'type': 'fill',
'source': 'assets',
'filter': ['match', ['geometry-type'], ['Polygon', 'MultiPolygon'], true, false],
'paint': {
'fill-color': [
'match', ['get', 'structure_type'],
['massif drainant', 'tranchee drainante', 'collecteur en beton poreux', 'empierrement sedimentation'], '#8d6e63',
['jardin de pluie', 'noue', 'toiture verte', 'bande filtrante', 'espace vert sur dalle'], '#009688',
['bassin sec/infiltration', 'bassin en eau', 'bassin orage', 'citerne', 'toiture stockante', 'SAUL-vide', 'SAUL-terre'], '#0277bd',
'#0288d1'
],
'fill-opacity': 0.6
}
});
// Polygons outline
map.addLayer({
'id': 'assets-polygon-borders',
'type': 'line',
'source': 'assets',
'filter': ['match', ['geometry-type'], ['Polygon', 'MultiPolygon'], true, false],
'paint': {
'line-color': [
'match', ['get', 'structure_type'],
['massif drainant', 'tranchee drainante', 'collecteur en beton poreux', 'empierrement sedimentation'], '#8d6e63',
['jardin de pluie', 'noue', 'toiture verte', 'bande filtrante', 'espace vert sur dalle'], '#009688',
['bassin sec/infiltration', 'bassin en eau', 'bassin orage', 'citerne', 'toiture stockante', 'SAUL-vide', 'SAUL-terre'], '#0277bd',
'#0288d1'
],
'line-width': 2
}
});
// Lines
map.addLayer({
'id': 'assets-lines',
'type': 'line',
'source': 'assets',
'filter': ['match', ['geometry-type'], ['LineString', 'MultiLineString'], true, false],
'paint': {
'line-color': [
'match', ['get', 'structure_type'],
['massif drainant', 'tranchee drainante', 'drain', 'collecteur en beton poreux', 'reseau egout separatif'], '#8d6e63',
['noue', 'jardin de pluie', 'bande filtrante'], '#009688',
['filet eau', 'caniveau infiltrant', 'gargouille de trottoir', 'avaloir de rail', 'trop plein'], '#0277bd',
'#0288d1'
],
'line-width': 3
}
});
// Points
map.addLayer({
'id': 'assets-points',
'type': 'circle',
'source': 'assets',
'filter': ['==', ['geometry-type'], 'Point'],
'paint': {
'circle-radius': 6,
'circle-color': '#0277bd',
'circle-stroke-color': '#ffffff',
'circle-stroke-width': 1.5
}
});
// 3. Zoom/fit bounds
const allFeatures = [];
if (featureGeom && featureGeom.type) {
allFeatures.push({
type: 'Feature',
geometry: featureGeom,
properties: {}
});
}
if (assetsData && assetsData.features) {
assetsData.features.forEach(f => {
if (f && f.geometry && f.geometry.type) {
allFeatures.push(f);
}
});
}
if (allFeatures.length > 0) {
const bounds = turf.bbox({
type: 'FeatureCollection',
features: allFeatures
});
map.fitBounds(bounds, {
padding: 40,
duration: 10
});
}
// 4. Popups & Interactivity
const popup = new maplibregl.Popup({
closeButton: true,
closeOnClick: true
});
function onAssetClick(e) {
const coordinates = e.features[0].geometry.type === 'Point'
? e.features[0].geometry.coordinates.slice()
: [e.lngLat.lng, e.lngLat.lat];
const props = e.features[0].properties;
const code = props.code;
const name = props.name;
const structType = props.structure_type_label || props.structure_type || '';
const url = props.detail_url;
let content = `<div style="padding: 5px;">
<strong>${code}</strong><br>
${name && name !== code ? `${name}<br>` : ''}
<span style="font-size: 0.85em; color: #555;">${structType}</span><br>
${url ? `<a href="${url}" class="btn btn-sm btn-primary text-white mt-1" style="font-size: 0.8em; padding: 2px 5px; text-decoration: none; border-radius: 4px; display: inline-block;">Détail</a>` : ''}
</div>`;
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
}
popup.setLngLat(coordinates).setHTML(content).addTo(map);
}
function setPointerCursor() {
map.getCanvas().style.cursor = 'pointer';
}
function resetCursor() {
map.getCanvas().style.cursor = '';
}
const assetLayers = ['assets-polygons', 'assets-lines', 'assets-points'];
assetLayers.forEach(layerId => {
map.on('mouseenter', layerId, setPointerCursor);
map.on('mouseleave', layerId, resetCursor);
map.on('click', layerId, onAssetClick);
});
});
// Event listener for tree toggles
document.addEventListener('click', (e) => {
const btn = e.target.closest('[data-bs-toggle="collapse"]');
if (!btn) return;
const targetSelector = btn.getAttribute('data-bs-target') || btn.getAttribute('href');
if (!targetSelector) return;
const target = document.querySelector(targetSelector);
if (!target) return;
if (btn.hasAttribute('data-bs-target')) {
target.addEventListener('shown.bs.collapse', () => btn.textContent = '▾', {once:true});
target.addEventListener('hidden.bs.collapse', () => btn.textContent = '▸', {once:true});
}
});
// 5. Document management (Add/History)
const fileInput = document.getElementById('fileInput');
const fileContainer = document.getElementById('fileFieldsContainer');
const documentTypeChoices = {
'as_built': 'As-built',
'other': 'Autre'
};
let documentConfig = { use_managed_documents: false, can_add_unique_tag_documents: false, config_url: null };
const configEl = document.getElementById('documentConfigData');
if (configEl) {
documentConfig = JSON.parse(configEl.textContent);
}
let availableTags = [];
let availableFolders = [];
async function loadDocumentConfig() {
if (!documentConfig.config_url) return;
try {
const response = await fetch(documentConfig.config_url);
const data = await response.json();
documentConfig.use_managed_documents = data.use_managed_documents;
documentConfig.can_add_unique_tag_documents = data.can_add_unique_tag_documents;
availableTags = data.tags;
availableFolders = data.folders;
} catch (e) {
console.error('Failed to load document config:', e);
}
}
loadDocumentConfig();
if (fileInput) {
fileInput.addEventListener('change', function() {
fileContainer.innerHTML = '';
const files = this.files;
for (let i = 0; i < files.length; i++) {
const file = files[i];
const index = i;
const row = document.createElement('div');
row.className = 'card p-3 mb-3 bg-light border-0';
const fileName = document.createElement('span');
fileName.className = 'fw-semibold text-secondary mb-2';
fileName.textContent = file.name;
row.appendChild(fileName);
if (documentConfig.use_managed_documents && availableTags && availableTags.length > 0) {
// Tag select
const tagSelect = document.createElement('select');
tagSelect.name = `tag_${index}`;
tagSelect.className = 'form-select mb-2 tag-select';
tagSelect.required = true;
const defaultOption = document.createElement('option');
defaultOption.value = '';
defaultOption.textContent = 'Sélectionner un type...';
tagSelect.appendChild(defaultOption);
availableTags.forEach(tag => {
const option = document.createElement('option');
option.value = tag.slug;
option.textContent = tag.name;
// Filter unique tags if user cannot add them
if (tag.is_unique && !documentConfig.can_add_unique_tag_documents) {
return;
}
tagSelect.appendChild(option);
});
row.appendChild(tagSelect);
// Folder select
const folderSelect = document.createElement('select');
folderSelect.name = `folder_${index}`;
folderSelect.className = 'form-select mb-2 folder-select';
availableFolders.forEach(folder => {
const option = document.createElement('option');
option.value = folder.id;
option.textContent = folder.name;
folderSelect.appendChild(option);
});
if (availableFolders.length > 1) {
row.appendChild(folderSelect);
} else {
const hiddenFolderInput = document.createElement('input');
hiddenFolderInput.type = 'hidden';
hiddenFolderInput.name = `folder_${index}`;
hiddenFolderInput.value = availableFolders[0] ? availableFolders[0].id : '';
row.appendChild(hiddenFolderInput);
}
// Hide folder select if unique tag is chosen
tagSelect.addEventListener('change', function() {
const selectedTagSlug = this.value;
const selectedTag = availableTags.find(t => t.slug === selectedTagSlug);
if (selectedTag && selectedTag.is_unique) {
folderSelect.style.display = 'none';
folderSelect.removeAttribute('required');
} else if (availableFolders.length > 1) {
folderSelect.style.display = 'block';
}
});
} else {
// Legacy / fallback select
const select = document.createElement('select');
select.name = `document_type_${index}`;
select.className = 'form-select mb-2';
for (const [value, label] of Object.entries(documentTypeChoices)) {
const option = document.createElement('option');
option.value = value;
option.textContent = label;
select.appendChild(option);
}
row.appendChild(select);
}
// Delete button
const deleteBtn = document.createElement('button');
deleteBtn.type = 'button';
deleteBtn.className = 'btn btn-sm btn-outline-danger mt-2 align-self-start';
deleteBtn.textContent = 'Retirer ce fichier';
deleteBtn.onclick = function() {
row.remove();
};
row.appendChild(deleteBtn);
fileContainer.appendChild(row);
}
const countInput = document.createElement('input');
countInput.type = 'hidden';
countInput.name = 'file_count';
countInput.value = files.length;
fileContainer.appendChild(countInput);
});
}
// History modal handling
const historyModal = document.getElementById('documentHistoryModal');
if (historyModal) {
historyModal.addEventListener('show.bs.modal', function (event) {
const button = event.relatedTarget;
const tagSlug = button.getAttribute('data-tag-slug');
const locationId = button.getAttribute('data-location-id');
const contentDiv = document.getElementById('documentHistoryContent');
contentDiv.innerHTML = '<div class="text-center p-4"><span class="spinner-border" role="status"></span></div>';
let url = `${document_history_url}?location_id=${locationId}&tag_slug=${tagSlug}`;
fetch(url)
.then(response => response.json())
.then(data => {
contentDiv.innerHTML = data.html;
})
.catch(error => {
contentDiv.innerHTML = '<div class="alert alert-danger">Erreur de chargement de l\'historique.</div>';
console.error('Error fetching history:', error);
});
});
}