400 lines
14 KiB
JavaScript
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);
|
|
});
|
|
});
|
|
}
|