1508 lines
No EOL
51 KiB
JavaScript
1508 lines
No EOL
51 KiB
JavaScript
var visible_locations = []
|
||
var visible_assets = []
|
||
var filter_geom = ''
|
||
|
||
var locations_geojson = {};
|
||
const locationsgeojsonloader = new GeojsonLoader();
|
||
|
||
if (!window.locations_outline_layer) {
|
||
locations_outline_layer = {
|
||
id: 'locations-outline',
|
||
type: 'line',
|
||
source: 'locations',
|
||
paint: {
|
||
'line-color': [
|
||
'case',
|
||
['boolean', ['feature-state', 'highlighted'], false], '#ffeb3b',
|
||
'#00000000' // Transparent si pas highlighté
|
||
],
|
||
'line-width': [
|
||
'case',
|
||
['boolean', ['feature-state', 'highlighted'], false], 1,
|
||
0
|
||
]
|
||
}
|
||
};
|
||
}
|
||
handleGeojsonMapLoading(init=true, locationsgeojsonloader, 'locations', [locations_layer_def,locations_outline_layer]);
|
||
|
||
// Reset table if error occurs when loading GeoJSON
|
||
locationsgeojsonloader.addEventListener("geojsonError", event => {
|
||
loadDataIntoLocationsTable([]);
|
||
});
|
||
|
||
// Load data into table when GeoJSON is loaded
|
||
locationsgeojsonloader.addEventListener("geojsonLoaded", event => {
|
||
var geojson_data = event.detail;
|
||
loadDataIntoLocationsTable(geojson_data.features);
|
||
locations_geojson = geojson_data;
|
||
});
|
||
|
||
|
||
var assets_geojson = {};
|
||
const assetsgeojsonloader = new GeojsonLoader();
|
||
|
||
handleGeojsonMapLoading(init=true, assetsgeojsonloader, 'assets', assets_layers_defs);
|
||
|
||
|
||
assetsgeojsonloader.addEventListener("geojsonError", event => {
|
||
loadDataIntoAssetsTable([]);
|
||
});
|
||
|
||
assetsgeojsonloader.addEventListener("geojsonLoaded", event => {
|
||
var geojson_data = event.detail;
|
||
loadDataIntoAssetsTable(geojson_data.features);
|
||
assets_geojson = geojson_data;
|
||
});
|
||
|
||
// Load Map
|
||
maploader.loadMap("map");
|
||
|
||
// Drawing selection tool
|
||
MapboxDraw.constants.classes.CANVAS = 'maplibregl-canvas';
|
||
MapboxDraw.constants.classes.CONTROL_BASE = 'maplibregl-ctrl';
|
||
MapboxDraw.constants.classes.CONTROL_PREFIX = 'maplibregl-ctrl-';
|
||
MapboxDraw.constants.classes.CONTROL_GROUP = 'maplibregl-ctrl-group';
|
||
MapboxDraw.constants.classes.ATTRIBUTION = 'maplibregl-ctrl-attrib';
|
||
|
||
const drawstyles=[{id:"gl-draw-polygon-fill",type:"fill",filter:["all",["==","$type","Polygon"],],paint:{"fill-color":["case",["==",["get","active"],"true"],"orange","blue",],"fill-opacity":["case",["==",["get","active"],"true"],0.1,0,]}},{id:"gl-draw-lines",type:"line",filter:["any",["==","$type","LineString"],["==","$type","Polygon"],],layout:{"line-cap":"round","line-join":"round"},paint:{"line-color":["case",["==",["get","active"],"true"],"orange","blue",],"line-dasharray":["literal",[.2,2]],"line-width":2}},{id:"gl-draw-point-outer",type:"circle",filter:["all",["==","$type","Point"],["==","meta","feature"],],paint:{"circle-radius":["case",["==",["get","active"],"true"],7,5,],"circle-color":"white"}},{id:"gl-draw-point-inner",type:"circle",filter:["all",["==","$type","Point"],["==","meta","feature"],],paint:{"circle-radius":["case",["==",["get","active"],"true"],5,3,],"circle-color":["case",["==",["get","active"],"true"],"orange","blue",]}},{id:"gl-draw-vertex-outer",type:"circle",filter:["all",["==","$type","Point"],["==","meta","vertex"],["!=","mode","simple_select"],],paint:{"circle-radius":["case",["==",["get","active"],"true"],7,5,],"circle-color":"white"}},{id:"gl-draw-vertex-inner",type:"circle",filter:["all",["==","$type","Point"],["==","meta","vertex"],["!=","mode","simple_select"],],paint:{"circle-radius":["case",["==",["get","active"],"true"],5,3,],"circle-color":"orange"}},{id:"gl-draw-midpoint",type:"circle",filter:["all",["==","meta","midpoint"],],paint:{"circle-radius":3,"circle-color":"orange"}},];
|
||
|
||
const draw = new MapboxDraw({
|
||
displayControlsDefault: false,
|
||
controls: {
|
||
polygon: true,
|
||
trash: true
|
||
},
|
||
styles: drawstyles
|
||
});
|
||
|
||
map.on('load', () => {
|
||
map.addControl(draw);
|
||
|
||
map.boxZoom.enable();
|
||
|
||
// Initial state
|
||
updateAutoZoneVisibility();
|
||
onMoveEndAuto(); // if already zoomed in on load, run once
|
||
|
||
// Gestion du curseur pour les vertices et midpoints
|
||
map.on('mousemove', (e) => {
|
||
const canvas = map.getCanvas();
|
||
|
||
// Récupérer les layers draw en utilisant des préfixes (car ils ont des suffixes .cold/.hot)
|
||
const allLayers = map.getStyle().layers;
|
||
const drawLayerPrefixes = ['gl-draw-polygon-fill', 'gl-draw-vertex-outer', 'gl-draw-midpoint'];
|
||
const existingLayers = allLayers
|
||
.filter(layer => drawLayerPrefixes.some(prefix => layer.id.startsWith(prefix)))
|
||
.map(layer => layer.id);
|
||
|
||
if (existingLayers.length === 0) return;
|
||
|
||
const features = map.queryRenderedFeatures(e.point, {
|
||
layers: existingLayers
|
||
});
|
||
|
||
// Variable pour savoir si on a appliqué un curseur draw
|
||
let drawCursorApplied = false;
|
||
|
||
if (features.length > 0) {
|
||
const feature = features[0];
|
||
// Si c'est un vertex ou midpoint
|
||
if (feature.properties.meta === 'vertex' || feature.properties.meta === 'midpoint') {
|
||
canvas.style.cursor = 'grab';
|
||
drawCursorApplied = true;
|
||
}
|
||
// Si c'est le polygone actif en mode direct_select
|
||
else if (feature.properties.active === 'true') {
|
||
canvas.style.cursor = 'move';
|
||
drawCursorApplied = true;
|
||
}
|
||
// Si c'est un polygone non activé (sélectionnable)
|
||
else if (feature.properties.active === 'false') {
|
||
canvas.style.cursor = 'pointer';
|
||
drawCursorApplied = true;
|
||
}
|
||
}
|
||
|
||
// Réinitialiser le curseur si on ne survole plus un élément draw
|
||
// et qu'on n'est pas en mode dessin
|
||
if (!drawCursorApplied) {
|
||
const currentMode = draw.getMode();
|
||
if (currentMode === 'draw_polygon') {
|
||
canvas.style.cursor = 'crosshair';
|
||
} else if (currentMode === 'simple_select' || currentMode === 'direct_select') {
|
||
// Ne pas interférer avec les curseurs des autres couches
|
||
// Réinitialiser uniquement si aucun autre événement ne gère le curseur
|
||
const otherFeatures = map.queryRenderedFeatures(e.point);
|
||
const hasOtherFeatures = otherFeatures.some(f =>
|
||
!existingLayers.includes(f.layer.id)
|
||
);
|
||
if (!hasOtherFeatures) {
|
||
canvas.style.cursor = '';
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
// Curseur pendant le drag d'un vertex
|
||
map.on('mousedown', (e) => {
|
||
const allLayers = map.getStyle().layers;
|
||
const drawLayerPrefixes = ['gl-draw-vertex-outer', 'gl-draw-midpoint'];
|
||
const existingLayers = allLayers
|
||
.filter(layer => drawLayerPrefixes.some(prefix => layer.id.startsWith(prefix)))
|
||
.map(layer => layer.id);
|
||
|
||
if (existingLayers.length === 0) return;
|
||
|
||
const features = map.queryRenderedFeatures(e.point, {
|
||
layers: existingLayers
|
||
});
|
||
|
||
if (features.length > 0) {
|
||
map.getCanvas().style.cursor = 'grabbing';
|
||
}
|
||
});
|
||
|
||
map.on('mouseup', () => {
|
||
const currentMode = draw.getMode();
|
||
if (currentMode === 'direct_select') {
|
||
map.getCanvas().style.cursor = 'move';
|
||
}
|
||
});
|
||
});
|
||
|
||
map.on('draw.create', (e) => {
|
||
const features = draw.getAll().features;
|
||
if (features.length > 1) {
|
||
// Supprime tous les autres sauf le dernier
|
||
const keep = e.features[0].id;
|
||
const idsToDelete = features
|
||
.filter(f => f.id !== keep)
|
||
.map(f => f.id);
|
||
draw.delete(idsToDelete);
|
||
}
|
||
// Réinitialiser le curseur après la création
|
||
map.getCanvas().style.cursor = '';
|
||
});
|
||
|
||
map.on('dblclick', (e) => {
|
||
e.preventDefault();
|
||
|
||
// Parfois le dragPan est désactivé à tort, on le réactive manuellement
|
||
if (!map.dragPan.isEnabled()) {
|
||
map.dragPan.enable();
|
||
}
|
||
});
|
||
|
||
|
||
map.on('draw.modechange', (e) => {
|
||
const safeModes = ['simple_select', 'direct_select'];
|
||
|
||
if (safeModes.includes(e.mode) && !map.boxZoom.isEnabled()) {
|
||
map.boxZoom.enable();
|
||
}
|
||
|
||
// Changer le curseur en fonction du mode
|
||
const canvas = map.getCanvas();
|
||
if (e.mode === 'draw_polygon') {
|
||
canvas.style.cursor = 'crosshair';
|
||
} else if (e.mode === 'direct_select') {
|
||
canvas.style.cursor = 'move';
|
||
} else {
|
||
canvas.style.cursor = '';
|
||
}
|
||
});
|
||
|
||
|
||
|
||
map.on('styledata', () => {
|
||
if (map.getLayer('assets') && map.getLayer('locations')) {
|
||
map.moveLayer('locations', 'assets');
|
||
}
|
||
});
|
||
|
||
|
||
// Récupération des IDs des layers 'assets'
|
||
const assetLayerIds = assets_layers_defs.map(l => l.id);
|
||
|
||
|
||
// Map layers events
|
||
//registerLayerMouseEnter(map, 'locations', popup, 'code', 14);
|
||
registerLayerMouseMove(map, 'locations', popup, 'code', 14);
|
||
registerLayerMouseLeave(map, 'locations', popup);
|
||
|
||
assetLayerIds.forEach(id => {
|
||
registerLayerMouseMove(map, id, popup, 'name', 17);
|
||
registerLayerMouseLeave(map, id, popup);
|
||
});
|
||
|
||
|
||
var locationshighlightState = { previousId: null }; // Track the previously highlighted feature
|
||
map.on('click', 'locations', (e) => {
|
||
const featureId = e.features[0].id;
|
||
const currentZoom = map.getZoom();
|
||
if (currentZoom > 13) {
|
||
// Activer l'onglet "locations" pour afficher le tableau
|
||
const locationsTab = document.getElementById('locations-table-tab');
|
||
if (locationsTab) {
|
||
const tab = new bootstrap.Tab(locationsTab);
|
||
tab.show();
|
||
}
|
||
|
||
// Highlight the corresponding row in the Bootstrap table
|
||
highlightRow('#locations-table', 'id', featureId);
|
||
|
||
// Update feature highlight on the map
|
||
highlightMapFeature(map, 'locations', featureId, locationshighlightState);
|
||
|
||
// Load assets GeoJSON for the selected location
|
||
// const locationId = featureId;
|
||
// const streetAssetsGeojsonUrl = `${assetsgeojsonUrl}?location=${locationId}`;
|
||
// assetsgeojsonloader.loadGeojson(streetAssetsGeojsonUrl);
|
||
// handleGeojsonMapLoading(init=false, assetsgeojsonloader, 'assets', [assets_layer_def]);
|
||
}
|
||
});
|
||
|
||
var assetshighlightState = { previousId: null }; // Track the previously highlighted feature
|
||
|
||
|
||
|
||
// Handler unique pour tous les layers "assets"
|
||
function onAssetClick(e) {
|
||
const f = e.features && e.features[0];
|
||
if (!f) return;
|
||
|
||
// Id robuste (si jamais .id n’est pas présent)
|
||
const featureId =
|
||
f.id ??
|
||
f.properties?.id ??
|
||
f.properties?.pk ??
|
||
f.properties?.objectid;
|
||
|
||
if (featureId == null) return;
|
||
|
||
if (map.getZoom() > 13) {
|
||
// Activer l'onglet "assets" pour afficher le tableau
|
||
const assetsTab = document.getElementById('assets-table-tab');
|
||
if (assetsTab) {
|
||
const tab = new bootstrap.Tab(assetsTab);
|
||
tab.show();
|
||
}
|
||
|
||
// surbrillance dans le tableau
|
||
highlightRow('#assets-table', 'id', featureId);
|
||
|
||
// surbrillance sur la carte
|
||
highlightMapFeature(map, 'assets', featureId, assetshighlightState);
|
||
}
|
||
}
|
||
|
||
// Enregistrement du handler sur chaque layer + curseur
|
||
assetLayerIds.forEach(id => {
|
||
map.on('click', id, onAssetClick);
|
||
map.on('mouseenter', id, () => (map.getCanvas().style.cursor = 'pointer'));
|
||
map.on('mouseleave', id, () => (map.getCanvas().style.cursor = ''));
|
||
});
|
||
|
||
|
||
|
||
|
||
|
||
|
||
// === config & state ===
|
||
const AUTO_ZOOM_THRESHOLD = 15;
|
||
const autoZoneWrapper = document.getElementById('auto-zone-wrapper');
|
||
const autoZoneCheck = document.getElementById('autoZoneCheck');
|
||
let autoZoneEnabled = true;
|
||
let lastBboxKey = null;
|
||
|
||
// mémoriser si on était au-dessus du seuil
|
||
let prevIsHighZoom = map.getZoom() >= AUTO_ZOOM_THRESHOLD;
|
||
|
||
|
||
function debounce(fn, wait=300) {
|
||
let t; return (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), wait); };
|
||
}
|
||
|
||
// Make a deterministic key for current bbox (rounded to 6 decimals)
|
||
function bboxKey(bounds) {
|
||
const sw = bounds.getSouthWest(), ne = bounds.getNorthEast();
|
||
const r = n => n.toFixed(6);
|
||
return `${r(sw.lng)},${r(sw.lat)}|${r(ne.lng)},${r(ne.lat)}`;
|
||
}
|
||
|
||
function bboxFeatureFromBounds(bounds) {
|
||
const sw = bounds.getSouthWest();
|
||
const ne = bounds.getNorthEast();
|
||
const nw = { lng: sw.lng, lat: ne.lat };
|
||
const se = { lng: ne.lng, lat: sw.lat };
|
||
return {
|
||
type: "Feature",
|
||
properties: { source: "viewbbox" },
|
||
geometry: { type: "Polygon", coordinates: [[
|
||
[sw.lng, sw.lat],[se.lng, se.lat],[ne.lng, ne.lat],[nw.lng, nw.lat],[sw.lng, sw.lat]
|
||
]]}
|
||
};
|
||
}
|
||
|
||
function updateAutoZoneVisibility() {
|
||
const isHigh = map.getZoom() >= AUTO_ZOOM_THRESHOLD;
|
||
|
||
// Affiche/masque la checkbox
|
||
if (isHigh) {
|
||
autoZoneWrapper.classList.remove('d-none');
|
||
} else {
|
||
autoZoneWrapper.classList.add('d-none');
|
||
}
|
||
|
||
// ⬇️ NEW: si on vient de passer de ≥ seuil à < seuil → reset ONCE
|
||
if (prevIsHighZoom && !isHigh) {
|
||
lastBboxKey = null; // invalide la clé de bbox
|
||
resetMapFilter(); // appelle ton reset exactement une fois sur la transition
|
||
}
|
||
|
||
prevIsHighZoom = isHigh; // maj l'état pour la prochaine fois
|
||
}
|
||
|
||
const triggerAutoZoneFilter = () => {
|
||
const geomFeature = bboxFeatureFromBounds(map.getBounds());
|
||
updateSelection(geomFeature); // your existing function
|
||
};
|
||
|
||
// Single place that triggers the filter
|
||
const onMoveEndAuto = debounce(() => {
|
||
if (!autoZoneEnabled) return;
|
||
if (map.getZoom() < AUTO_ZOOM_THRESHOLD) return;
|
||
|
||
const key = bboxKey(map.getBounds());
|
||
if (key !== lastBboxKey) {
|
||
lastBboxKey = key;
|
||
triggerAutoZoneFilter();
|
||
}
|
||
}, 250);
|
||
|
||
// Wire events
|
||
map.on('zoomend', updateAutoZoneVisibility); // visibility only
|
||
map.on('moveend', onMoveEndAuto);
|
||
|
||
autoZoneCheck.addEventListener('change', (e) => {
|
||
autoZoneEnabled = e.target.checked;
|
||
lastBboxKey = null; // ensure a fresh trigger on next move
|
||
if (autoZoneEnabled && map.getZoom() >= AUTO_ZOOM_THRESHOLD) {
|
||
onMoveEndAuto(); // schedule one run
|
||
}
|
||
if (!autoZoneEnabled) {
|
||
resetMapFilter();
|
||
}
|
||
});
|
||
|
||
document.addEventListener("DOMContentLoaded", () => {
|
||
const autoZoneCheck = document.getElementById("autoZoneCheck");
|
||
if (autoZoneCheck) {
|
||
autoZoneCheck.checked = true;
|
||
}
|
||
});
|
||
|
||
|
||
|
||
|
||
// Charger les données initiales
|
||
let params = new URLSearchParams(window.location.search).toString();
|
||
locationsgeojsonloader.loadGeojson(locationsgeojsonUrl+`?${params}`);
|
||
assetsgeojsonloader.loadGeojson(assetsgeojsonUrl+`?${params}`);
|
||
|
||
|
||
|
||
function actionsFormatter(value, row, index) {
|
||
if (selected_locations_set.has(row.id)) {
|
||
icon_select = 'bi-check-square-fill';
|
||
} else {
|
||
icon_select = 'bi-square';
|
||
}
|
||
|
||
resp = `<button class="btn btn-sm btn-light location-btn-toggle" data-id="${row.id}" data-bs-toggle="tooltip" title="(Dé)sélectionner la localisation">
|
||
<i class="bi ${icon_select}"></i>
|
||
</button>`;
|
||
|
||
if (row.lon) {
|
||
resp = resp +
|
||
`<button class="btn btn-sm btn-light zoom-btn ms-1" data-lon="${row.lon}" data-lat="${row.lat}" data-bs-toggle="tooltip" title="Zoom">
|
||
<i class="bi bi-geo-alt"></i>
|
||
</button>`;
|
||
}
|
||
|
||
return resp;
|
||
}
|
||
|
||
function codeFormatterLocations(value, row, index) {
|
||
if (row.detail_url) {
|
||
return `<a href="${row.detail_url}" class="text-decoration-none">${value}</a>`;
|
||
}
|
||
return value;
|
||
}
|
||
|
||
|
||
function actionsFormatterAssets(value, row, index) {
|
||
|
||
const key = assetKey(row.asset_type, row.id);
|
||
const isSelected = selected_assets_set.has(key);
|
||
const btnClass = 'btn-light';
|
||
const iconClass = isSelected ? 'bi-check-square' : 'bi-square';
|
||
const selectedValue = isSelected ? 'true' : 'false';
|
||
|
||
resp = `
|
||
<button class="btn btn-sm ${btnClass} asset-btn-toggle"
|
||
data-id="${row.id}"
|
||
data-asset_type="${row.asset_type}"
|
||
data-key="${key}"
|
||
data-selected="${selectedValue}"
|
||
data-bs-toggle="tooltip"
|
||
title="${isSelected ? "Désélectionner l'asset" : "Sélectionner l'asset"}">
|
||
<i class="bi ${iconClass}"></i>
|
||
</button>`;
|
||
|
||
if (row.lon) {
|
||
resp = resp +
|
||
`<button class="btn btn-sm btn-light zoom-btn ms-1" data-lon="${row.lon}" data-lat="${row.lat}" data-bs-toggle="tooltip" title="Zoom">
|
||
<i class="bi bi-geo-alt"></i>
|
||
</button>`;
|
||
}
|
||
|
||
return resp;
|
||
}
|
||
|
||
function codeFormatterAssets(value, row, index) {
|
||
if (row.detail_url) {
|
||
return `<a href="${row.detail_url}" class="text-decoration-none">${value}</a>`;
|
||
}
|
||
return value;
|
||
}
|
||
|
||
|
||
|
||
|
||
// Loads features into the locations table
|
||
function loadDataIntoLocationsTable(features) {
|
||
const tableData = features.map((feature) => {
|
||
const row = {
|
||
actions: '', // Colonne pour les actions (boutons, icônes, etc.)
|
||
id: feature.id,
|
||
code: feature.properties.code,
|
||
lon: feature.properties.lon,
|
||
lat: feature.properties.lat
|
||
};
|
||
|
||
// Ajoute dynamiquement les autre propriétés
|
||
locations_property_fields.forEach(field => {
|
||
row[field] = feature.properties[field];
|
||
});
|
||
|
||
row.detail_url = feature.properties.detail_url || null;
|
||
|
||
return row;
|
||
});
|
||
|
||
$('#locations-spinner').hide();
|
||
$('#locations-table-container').show();
|
||
$('#locations-table').bootstrapTable('load', tableData);
|
||
}
|
||
|
||
// Loads features into the assets table
|
||
function loadDataIntoAssetsTable(features) {
|
||
const tableData = features.map((feature) => {
|
||
const row = {
|
||
actions: '', // Colonne pour les actions (boutons, icônes, etc.)
|
||
id: feature.id,
|
||
asset_type: feature.properties.asset_type,
|
||
code: feature.properties.code,
|
||
status: '<i class="bi bi-circle-fill status_'+feature.properties.status+'"></i>',
|
||
lon: feature.properties.lon,
|
||
lat: feature.properties.lat
|
||
};
|
||
|
||
// Ajoute dynamiquement les autre propriétés
|
||
assets_property_fields.forEach(field => {
|
||
row[field] = feature.properties[field];
|
||
});
|
||
|
||
row.detail_url = feature.properties.detail_url || null;
|
||
|
||
return row;
|
||
});
|
||
|
||
$('#assets-spinner').hide();
|
||
$('#assets-table-container').show();
|
||
$('#assets-table').bootstrapTable('load', tableData);
|
||
}
|
||
|
||
|
||
|
||
function redirectToThematic() {
|
||
const select = document.getElementById('theme-select');
|
||
const selectedValue = select.value;
|
||
|
||
if (selectedValue) {
|
||
// Rediriger vers l'URL sélectionnée
|
||
window.location.href = selectedValue;
|
||
}
|
||
}
|
||
|
||
|
||
$( document ).ready(function() {
|
||
|
||
// Apply the correct width to the dropdown-menu
|
||
const parent_filter_dropdown = $('#col-table');
|
||
const parentWidth = parent_filter_dropdown.width() - 20;
|
||
// $('#filter-dropdown-wrapper').find('.dropdown-menu').width(`${parentWidth}px`);
|
||
|
||
function calculateTableHeight() {
|
||
const windowHeight = $(window).height(); // Total window height
|
||
const offsetTop = 285; // Y position of the table
|
||
const footerHeight = $('.footer').outerHeight() || 0;
|
||
return windowHeight - offsetTop - footerHeight - 20;
|
||
}
|
||
|
||
$('#locations-table').bootstrapTable({
|
||
columns: [
|
||
{ field: 'actions', title: '', formatter:'actionsFormatter', class: 'text-nowrap', width: 90 },
|
||
{ field: 'code', title: gettext('Code'), class: 'text-nowrap', formatter: 'codeFormatterLocations' },
|
||
...locations_columns // Colonnes spécifiques
|
||
],
|
||
height: calculateTableHeight(), // Apply computed height
|
||
onClickRow: function (row, $element) {
|
||
// Remove highlight from all previously highlighted cells
|
||
$('#locations-table').find('tbody td').removeClass('rowhighlight');
|
||
// Apply the highlight class to all `<td>` cells in the row
|
||
$element.find('td').addClass('rowhighlight');
|
||
|
||
// Update feature highlight on the map
|
||
highlightMapFeature(map, 'locations', row.id, locationshighlightState);
|
||
|
||
// Load assets GeoJSON for the selected location
|
||
// const locationId = row.id;
|
||
// const locationAssetsGeojsonUrl = `${assetsgeojsonUrl}?location=${locationId}`;
|
||
// assetsgeojsonloader.loadGeojson(locationAssetsGeojsonUrl);
|
||
// handleGeojsonMapLoading(init=false, assetsgeojsonloader, 'assets', [assets_layer_def]);
|
||
}
|
||
});
|
||
|
||
$('#assets-table').bootstrapTable({
|
||
columns: [
|
||
{ field: 'actions', title: '', formatter:'actionsFormatterAssets', width: 90 },
|
||
{ field: 'code', title: gettext('Code'), formatter: 'codeFormatterAssets' },
|
||
...assets_columns, // Colonnes spécifiques
|
||
{ field: 'status', title: gettext('Statut'), align: 'center' }
|
||
|
||
],
|
||
height: calculateTableHeight(), // Apply computed height
|
||
onClickRow: function (row, $element) {
|
||
// Remove highlight from all previously highlighted cells
|
||
$('#assets-table').find('tbody td').removeClass('rowhighlight');
|
||
// Apply the highlight class to all `<td>` cells in the row
|
||
$element.find('td').addClass('rowhighlight');
|
||
|
||
// Update feature highlight on the map
|
||
highlightMapFeature(map, 'assets', row.id, assetshighlightState);
|
||
}
|
||
});
|
||
|
||
|
||
$('#theme-select').on('change', function () {
|
||
redirectToThematic();
|
||
});
|
||
|
||
// reset view when tab is clicked (in case of window resize)
|
||
$('#locations-table-tab').on('click', function () {
|
||
$('#locations-table').bootstrapTable('resetView');
|
||
});
|
||
$('#assets-table-tab').on('click', function () {
|
||
$('#assets-table').bootstrapTable('resetView');
|
||
});
|
||
|
||
// Apply filters
|
||
document.getElementById('apply-filters').addEventListener('click', function(e) {
|
||
|
||
// const form = document.getElementById('filter-form');
|
||
// const formData = new FormData(form);
|
||
// const params = new URLSearchParams();
|
||
// for (const [key, value] of formData.entries()) {
|
||
// if (value.trim() !== '') { // Vérifie que le champ n'est pas vide
|
||
// params.append(key, value);
|
||
// }
|
||
// }
|
||
|
||
// // Update URL
|
||
// const newUrl = `${window.location.pathname}?${params.toString()}`;
|
||
// window.history.pushState({}, '', newUrl);
|
||
|
||
// Load filtered data
|
||
// handleGeojsonMapLoading(init=false, locationsgeojsonloader, 'locations', [locations_layer_def]);
|
||
// locationsgeojsonloader.loadGeojson(locationsgeojsonUrl+`?${params.toString()}`);
|
||
updateLocations();
|
||
|
||
// Trouver le dropdown parent et le refermer
|
||
const dropdownEl = this.closest('.dropdown');
|
||
const dropdown = bootstrap.Dropdown.getInstance(dropdownEl.querySelector('[data-bs-toggle="dropdown"]'));
|
||
if (dropdown) {
|
||
dropdown.hide(); // ✅ ferme proprement le dropdown
|
||
}
|
||
});
|
||
|
||
// Reset filters
|
||
document.getElementById('reset-filters').addEventListener('click', function(e) {
|
||
const form = document.getElementById('filter-form');
|
||
|
||
//form.reset(); // Reset form fields
|
||
// Parcourir tous les champs du formulaire et les réinitialiser
|
||
form.querySelectorAll('input, select, textarea').forEach(field => {
|
||
if (field.tagName.toLowerCase() === 'select') {
|
||
field.selectedIndex = -1; // aucune option sélectionnée
|
||
} else if (field.type === 'checkbox' || field.type === 'radio') {
|
||
field.checked = false; // décocher
|
||
} else {
|
||
field.value = ''; // vider le texte / nombre / date
|
||
}
|
||
});
|
||
|
||
// // Remove URL parameters
|
||
// window.history.pushState({}, '', window.location.pathname);
|
||
|
||
// Reload all data without filters
|
||
// handleGeojsonMapLoading(init=false, locationsgeojsonloader, 'locations', [locations_layer_def]);
|
||
// locationsgeojsonloader.loadGeojson(locationsgeojsonUrl);
|
||
updateLocations();
|
||
|
||
// Trouver le dropdown parent et le refermer
|
||
const dropdownEl = this.closest('.dropdown');
|
||
const dropdown = bootstrap.Dropdown.getInstance(dropdownEl.querySelector('[data-bs-toggle="dropdown"]'));
|
||
if (dropdown) {
|
||
dropdown.hide(); // ✅ ferme proprement le dropdown
|
||
}
|
||
});
|
||
|
||
|
||
// Apply assets filters
|
||
document.getElementById('apply-assets-filters').addEventListener('click', function(e) {
|
||
// Load filtered data
|
||
updateAssets();
|
||
|
||
// Trouver le dropdown parent et le refermer
|
||
const dropdownEl = this.closest('.dropdown');
|
||
const dropdown = bootstrap.Dropdown.getInstance(dropdownEl.querySelector('[data-bs-toggle="dropdown"]'));
|
||
if (dropdown) {
|
||
dropdown.hide(); // ✅ ferme proprement le dropdown
|
||
}
|
||
});
|
||
|
||
// Reset filters
|
||
document.getElementById('reset-assets-filters').addEventListener('click', function(e) {
|
||
const form = document.getElementById('assets-filter-form');
|
||
|
||
// form.reset(); // Reset form fields
|
||
// Parcourir tous les champs du formulaire et les réinitialiser
|
||
form.querySelectorAll('input, select, textarea').forEach(field => {
|
||
if (field.tagName.toLowerCase() === 'select') {
|
||
field.selectedIndex = -1; // aucune option sélectionnée
|
||
} else if (field.type === 'checkbox' || field.type === 'radio') {
|
||
field.checked = false; // décocher
|
||
} else {
|
||
field.value = ''; // vider le texte / nombre / date
|
||
}
|
||
});
|
||
|
||
updateAssets();
|
||
|
||
// Trouver le dropdown parent et le refermer
|
||
const dropdownEl = this.closest('.dropdown');
|
||
const dropdown = bootstrap.Dropdown.getInstance(dropdownEl.querySelector('[data-bs-toggle="dropdown"]'));
|
||
if (dropdown) {
|
||
dropdown.hide(); // ✅ ferme proprement le dropdown
|
||
}
|
||
});
|
||
|
||
|
||
// Filter tables by map bounds
|
||
// $("#filterzone").on( "click", function(e) {
|
||
// e.preventDefault();
|
||
// filterTableByMapBounds(map, locations_geojson, loadDataIntoLocationsTable);
|
||
// filterTableByMapBounds(map, assets_geojson, loadDataIntoAssetsTable);
|
||
// });
|
||
|
||
|
||
// Register zoom action to the tables
|
||
registerZoomAction('#locations-table', '.zoom-btn', map, 17);
|
||
registerZoomAction('#assets-table', '.zoom-btn', map, 18);
|
||
|
||
// Register location selection action to table locations
|
||
registerSelectLocationAction('#locations-table', '.location-btn-toggle');
|
||
});
|
||
|
||
|
||
function registerSelectLocationAction(tableId, buttonClass) {
|
||
$(tableId).on('click', buttonClass, function () {
|
||
const $button = $(this);
|
||
const locationId = $(this).data('id'); // Retrieve the ID from the button's data attribute
|
||
|
||
if (locationId) {
|
||
toggleSelectedLocation(locationId, $button);
|
||
}
|
||
});
|
||
}
|
||
|
||
function toggleSelectedLocation(locationId, $button) {
|
||
if (selected_locations_set.has(locationId)) {
|
||
selected_locations_set.delete(locationId);
|
||
$button
|
||
.find('i').removeClass('bi-check-square-fill').addClass('bi-square');
|
||
} else {
|
||
selected_locations_set.add(locationId);
|
||
$button
|
||
.find('i').removeClass('bi-square').addClass('bi-check-square-fill');
|
||
}
|
||
updateSelectedLocationsInputAndStatus();
|
||
|
||
if (preventive_draft_id !== null && preventive_draft_id !== 'None' && preventive_draft_id !== '' && preventive_element_type == 'locations') {
|
||
// not necessary to search assets if it concerns a selection for preventive interventions
|
||
} else {
|
||
updateAssets();
|
||
}
|
||
}
|
||
|
||
function updateSelectedLocationsInputAndStatus() {
|
||
const previnputField = document.getElementById("input-preventive-selected-locations-ids");
|
||
const statusBar = document.getElementById("locations-selection-status");
|
||
const ids = Array.from(selected_locations_set);
|
||
if (previnputField) previnputField.value = ids.join(",");
|
||
statusBar.innerText = interpolate(
|
||
ngettext('%(count)s localisation sélectionnée',
|
||
'%(count)s localisations sélectionnées',
|
||
ids.length),
|
||
{ count: ids.length },
|
||
true
|
||
);
|
||
}
|
||
|
||
function updateSelectedAssetsInputAndStatus() {
|
||
const statusBar = document.getElementById("assets-selection-status");
|
||
const assets = Array.from(selected_assets_set);
|
||
statusBar.innerText = interpolate(
|
||
ngettext('%(count)s asset sélectionné',
|
||
'%(count)s assets sélectionnés',
|
||
assets.length),
|
||
{ count: assets.length },
|
||
true
|
||
);
|
||
}
|
||
|
||
// Selection of locations
|
||
document.addEventListener("DOMContentLoaded", function () {
|
||
|
||
document.getElementById("locations-select-all").addEventListener("click", () => {
|
||
const allRows = $('#locations-table').bootstrapTable('getData');
|
||
allRows.forEach(row => {
|
||
selected_locations_set.add(parseInt(row.id));
|
||
const btn = document.querySelector(`.location-btn-toggle[data-id="${row.id}"]`);
|
||
if (btn) {
|
||
$(btn).find('i').removeClass('bi-square').addClass('bi-check-square-fill');
|
||
}
|
||
});
|
||
updateSelectedLocationsInputAndStatus();
|
||
if (preventive_draft_id !== null && preventive_draft_id !== 'None' && preventive_draft_id !== '' && preventive_element_type == 'locations') {
|
||
// not necessary to search assets if it concerns a selection for preventive interventions
|
||
} else {
|
||
updateAssets();
|
||
}
|
||
});
|
||
|
||
document.getElementById("locations-deselect-all").addEventListener("click", () => {
|
||
selected_locations_set.clear();
|
||
const allRows = $('#locations-table').bootstrapTable('getData');
|
||
allRows.forEach(row => {
|
||
const btn = document.querySelector(`.location-btn-toggle[data-id="${row.id}"]`);
|
||
if (btn) {
|
||
$(btn).find('i').removeClass('bi-check-square-fill').addClass('bi-square');
|
||
}
|
||
});
|
||
updateSelectedLocationsInputAndStatus();
|
||
if (preventive_draft_id !== null && preventive_draft_id !== 'None' && preventive_draft_id !== '' && preventive_element_type == 'locations') {
|
||
// not necessary to search assets if it concerns a selection for preventive interventions
|
||
} else {
|
||
updateAssets();
|
||
}
|
||
});
|
||
|
||
// Initial state
|
||
updateSelectedLocationsInputAndStatus();
|
||
|
||
|
||
document.getElementById("assets-select-all").addEventListener("click", () => {
|
||
const allRows = $('#assets-table').bootstrapTable('getData');
|
||
console.log(allRows);
|
||
allRows.forEach(row => {
|
||
selected_assets_set.add(assetKey(row.asset_type, row.id));
|
||
const btn = document.querySelector(`.asset-btn-toggle[data-id="${row.id}"]`);
|
||
if (btn) {
|
||
$(btn).find('i').removeClass('bi-square').addClass('bi-check-square');
|
||
}
|
||
});
|
||
updateSelectedAssetsInputAndStatus();
|
||
});
|
||
|
||
document.getElementById("assets-deselect-all").addEventListener("click", () => {
|
||
selected_assets_set.clear();
|
||
const allRows = $('#assets-table').bootstrapTable('getData');
|
||
allRows.forEach(row => {
|
||
const btn = document.querySelector(`.asset-btn-toggle[data-id="${row.id}"]`);
|
||
if (btn) {
|
||
$(btn).find('i').removeClass('bi-check-square').addClass('bi-square');
|
||
}
|
||
});
|
||
updateSelectedAssetsInputAndStatus();
|
||
});
|
||
|
||
// Initial state
|
||
updateSelectedAssetsInputAndStatus();
|
||
});
|
||
|
||
|
||
document.getElementById("assets-table").addEventListener('click', (e) => {
|
||
const btn = e.target.closest('.asset-btn-toggle');
|
||
if (!btn) return;
|
||
|
||
const key = btn.dataset.key;
|
||
const icon = btn.querySelector('i');
|
||
|
||
if (selected_assets_set.has(key)) {
|
||
selected_assets_set.delete(key);
|
||
btn.dataset.selected = 'false';
|
||
icon.classList.remove('bi-check-square');
|
||
icon.classList.add('bi-square');
|
||
btn.title = gettext("Sélectionner l'asset");
|
||
} else {
|
||
selected_assets_set.add(key);
|
||
btn.dataset.selected = 'true';
|
||
icon.classList.remove('bi-square');
|
||
icon.classList.add('bi-check-square');
|
||
btn.title = gettext("Désélectionner l'asset");
|
||
}
|
||
|
||
updateSelectedAssetsInputAndStatus();
|
||
});
|
||
|
||
// Avant soumission, sérialiser le Set -> JSON dans le champ caché
|
||
if (document.getElementById('preventive-select-form')) {
|
||
document.getElementById('preventive-select-form').addEventListener('submit', () => {
|
||
const assets = Array.from(selected_assets_set).map(k => {
|
||
const [model, id] = k.split(':');
|
||
return { model, object_id: Number(id) };
|
||
});
|
||
document.getElementById('input-preventive-selected-assets').value = JSON.stringify(assets);
|
||
});
|
||
}
|
||
|
||
|
||
|
||
// Positionnne le filtre dans la toolbar du tableau
|
||
document.addEventListener("DOMContentLoaded", function () {
|
||
$('#locations-table').on('post-body.bs.table', function () {
|
||
const $toolbar = $('#locations-table-container').find('.bootstrap-table').find('.fixed-table-toolbar');
|
||
|
||
// Ne pas réinsérer si déjà fait
|
||
if ($toolbar.find('#filter-dropdown-wrapper').length === 0) {
|
||
$('#filter-dropdown-wrapper')
|
||
.removeClass('d-none')
|
||
.prependTo($toolbar);
|
||
}
|
||
});
|
||
|
||
$('#assets-table').on('post-body.bs.table', function () {
|
||
const $toolbar = $('#assets-table-container').find('.bootstrap-table').find('.fixed-table-toolbar');
|
||
|
||
// Ne pas réinsérer si déjà fait
|
||
if ($toolbar.find('#filter-assets-dropdown-wrapper').length === 0) {
|
||
$('#filter-assets-dropdown-wrapper')
|
||
.removeClass('d-none')
|
||
.prependTo($toolbar);
|
||
}
|
||
});
|
||
});
|
||
|
||
|
||
|
||
|
||
function updateLocations() {
|
||
|
||
const alertBox = document.getElementById("location-alert");
|
||
|
||
const form = document.getElementById('filter-form');
|
||
const formData = new FormData(form);
|
||
|
||
// Update URL
|
||
if (preventive_draft_id == 'None') {
|
||
updateURLFromForms('filter-form', 'assets-filter-form');
|
||
}
|
||
|
||
// On convertit FormData en objet
|
||
const filters = {};
|
||
for (const [key, value] of formData.entries()) {
|
||
if (value.trim() !== '') {
|
||
// Si le champ existe déjà (checkbox, multiple select), on stocke en tableau
|
||
if (filters[key]) {
|
||
if (!Array.isArray(filters[key])) {
|
||
filters[key] = [filters[key]];
|
||
}
|
||
filters[key].push(value);
|
||
} else {
|
||
filters[key] = value;
|
||
}
|
||
}
|
||
}
|
||
|
||
try {
|
||
parsedGeom = JSON.parse(filter_geom);
|
||
} catch (e) {
|
||
parsedGeom = null;
|
||
}
|
||
|
||
fetch(locationsgeojsonUrl, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': getCookie('csrftoken') // si CSRF est activé
|
||
},
|
||
body: JSON.stringify({
|
||
filter_geom: parsedGeom, // Ajouter la géométrie
|
||
q: (document.getElementById('assets-search-input') ? document.getElementById('assets-search-input').value.trim() : ''),
|
||
search_mode: (document.querySelector('input[name="assets-search-mode"]:checked') ? document.querySelector('input[name="assets-search-mode"]:checked').value : 'and'),
|
||
...filters // Ajout de tous les champs de filtre
|
||
})
|
||
})
|
||
.then(response => response.json())
|
||
.then(geojsonData => {
|
||
if (geojsonData.error) {
|
||
// Affiche l'erreur dans le div Bootstrap
|
||
alertBox.textContent = geojsonData.error;
|
||
alertBox.classList.remove("d-none");
|
||
return;
|
||
}
|
||
|
||
alertBox.classList.add("d-none"); // Cache l'alerte si tout va bien
|
||
|
||
if (map.getSource('locations')) {
|
||
map.getSource('locations').setData(geojsonData);
|
||
}
|
||
loadDataIntoLocationsTable(geojsonData.features);
|
||
})
|
||
.catch(error => {
|
||
alertBox.textContent = gettext("Erreur lors de la récupération des données.");
|
||
alertBox.classList.remove("d-none");
|
||
console.error("Erreur :", error);
|
||
});
|
||
}
|
||
|
||
|
||
function updateAssets() {
|
||
const selectedLocationsIds = Array.from(selected_locations_set);
|
||
|
||
const alertBox = document.getElementById("asset-alert");
|
||
|
||
const form = document.getElementById('assets-filter-form');
|
||
const formData = new FormData(form);
|
||
|
||
// Update URL
|
||
updateURLFromForms('filter-form', 'assets-filter-form');
|
||
|
||
// On convertit FormData en objet
|
||
const filters = {};
|
||
for (const [key, value] of formData.entries()) {
|
||
if (value.trim() !== '') {
|
||
// Si le champ existe déjà (checkbox, multiple select), on stocke en tableau
|
||
if (filters[key]) {
|
||
if (!Array.isArray(filters[key])) {
|
||
filters[key] = [filters[key]];
|
||
}
|
||
filters[key].push(value);
|
||
} else {
|
||
filters[key] = value;
|
||
}
|
||
}
|
||
}
|
||
|
||
try {
|
||
parsedGeom = JSON.parse(filter_geom);
|
||
} catch (e) {
|
||
parsedGeom = null;
|
||
}
|
||
|
||
fetch(assetsgeojsonUrl, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': getCookie('csrftoken') // si CSRF est activé
|
||
},
|
||
body: JSON.stringify({
|
||
locations_ids: selectedLocationsIds,
|
||
filter_geom: parsedGeom,
|
||
q: (document.getElementById('assets-search-input') ? document.getElementById('assets-search-input').value.trim() : ''),
|
||
search_mode: (document.querySelector('input[name="assets-search-mode"]:checked') ? document.querySelector('input[name="assets-search-mode"]:checked').value : 'and'),
|
||
...filters // Ajout de tous les champs de filtre
|
||
})
|
||
})
|
||
.then(response => response.json())
|
||
.then(geojsonData => {
|
||
if (geojsonData.error) {
|
||
// Affiche l'erreur dans le div Bootstrap
|
||
alertBox.textContent = geojsonData.error;
|
||
alertBox.classList.remove("d-none");
|
||
return;
|
||
}
|
||
|
||
alertBox.classList.add("d-none"); // Cache l'alerte si tout va bien
|
||
|
||
if (map.getSource('assets')) {
|
||
map.getSource('assets').setData(geojsonData);
|
||
}
|
||
loadDataIntoAssetsTable(geojsonData.features);
|
||
})
|
||
.catch(error => {
|
||
alertBox.textContent = gettext("Erreur lors de la récupération des données.");
|
||
alertBox.classList.remove("d-none");
|
||
console.error("Erreur :", error);
|
||
});
|
||
}
|
||
|
||
|
||
function insertNoAssetsRowMessage() {
|
||
const $table = $('#assets-table');
|
||
const $tbody = $table.find('tbody');
|
||
|
||
// Supprime les messages précédents si existants
|
||
$tbody.find('tr.no-assets-message').remove();
|
||
$tbody.find('tr.no-records-found').remove();
|
||
|
||
if ($tbody.find('tr').length === 0) {
|
||
const colspan = $table.find('thead th').length || 1;
|
||
|
||
// Détecter si une recherche ou un filtre est actif
|
||
const searchVal = (document.getElementById('assets-search-input')?.value || '').trim();
|
||
const hasSelectedLocations = selected_locations_set.size > 0;
|
||
const assetFiltersForm = document.getElementById('assets-filter-form');
|
||
let hasAssetFilters = false;
|
||
if (assetFiltersForm) {
|
||
const formData = new FormData(assetFiltersForm);
|
||
for (const [, value] of formData.entries()) {
|
||
if (value.trim() !== '') { hasAssetFilters = true; break; }
|
||
}
|
||
}
|
||
|
||
let noResultsText;
|
||
if (!searchVal && !hasSelectedLocations && !hasAssetFilters) {
|
||
noResultsText = gettext('Sélectionnez des localisations ou lancez une recherche pour afficher les assets.');
|
||
} else {
|
||
noResultsText = gettext('Aucun asset ne correspond aux critères de recherche.');
|
||
}
|
||
|
||
const $msgRow = $(`
|
||
<tr class="no-assets-message">
|
||
<td colspan="${colspan}" class="text-center text-muted">
|
||
${noResultsText}
|
||
</td>
|
||
</tr>
|
||
`);
|
||
$tbody.append($msgRow);
|
||
}
|
||
}
|
||
|
||
// Hook appelé après chaque mise à jour de la table
|
||
$('#assets-table').on('post-body.bs.table', function () {
|
||
insertNoAssetsRowMessage();
|
||
});
|
||
|
||
|
||
|
||
|
||
map.on('draw.create', e => updateSelection(e.features[0]));
|
||
map.on('draw.update', e => updateSelection(e.features[0]));
|
||
map.on('draw.delete', e => {
|
||
resetMapFilter();
|
||
});
|
||
|
||
|
||
|
||
function updateSelection(geometry) {
|
||
filter_geom = JSON.stringify(geometry);
|
||
updateLocations();
|
||
updateAssets();
|
||
}
|
||
|
||
|
||
// Réinitialiser le filtre carte
|
||
function resetMapFilter() {
|
||
filter_geom = '';
|
||
updateLocations();
|
||
updateAssets();
|
||
}
|
||
|
||
|
||
|
||
|
||
function updateURLFromForms(...formIds) {
|
||
const params = new URLSearchParams();
|
||
|
||
formIds.forEach(formId => {
|
||
const form = document.getElementById(formId);
|
||
if (!form) return;
|
||
|
||
const formData = new FormData(form);
|
||
|
||
for (const [key, value] of formData.entries()) {
|
||
if (value.trim() !== '') {
|
||
params.append(key, value); // conserve les champs multiples
|
||
}
|
||
}
|
||
});
|
||
|
||
const newUrl = `${window.location.pathname}?${params.toString()}`;
|
||
window.history.pushState({}, '', newUrl);
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
// --- Layout Management ---
|
||
|
||
const col_table = document.getElementById("col-table");
|
||
const col_map = document.getElementById("col-map");
|
||
|
||
const layoutBtnTable = document.getElementById("layout-btn-table");
|
||
const layoutBtnBoth = document.getElementById("layout-btn-both");
|
||
const layoutBtnMap = document.getElementById("layout-btn-map");
|
||
|
||
// Current layout state
|
||
let currentAssetLayout = typeof INITIAL_ASSET_LAYOUT !== 'undefined' ? INITIAL_ASSET_LAYOUT : 'both';
|
||
|
||
// Function to save layout preference to backend
|
||
function saveAssetLayoutPreference(layout) {
|
||
if (typeof saveAssetLayoutUrl === 'undefined') return;
|
||
fetch(saveAssetLayoutUrl, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': getCookie('csrftoken')
|
||
},
|
||
body: JSON.stringify({ layout: layout })
|
||
}).catch(err => console.warn('Could not save asset layout preference:', err));
|
||
}
|
||
|
||
// Function to apply layout
|
||
function applyAssetLayout(layout, savePreference = true) {
|
||
currentAssetLayout = layout;
|
||
|
||
// Reset classes
|
||
col_table.classList.remove("col-11", "col-12", "col-md-6", "order-2", "d-none");
|
||
col_map.classList.remove("col-11", "col-12", "col-md-6", "order-2", "d-none");
|
||
|
||
// Reset button active states
|
||
layoutBtnTable.classList.remove("text-primary");
|
||
layoutBtnBoth.classList.remove("text-primary");
|
||
layoutBtnMap.classList.remove("text-primary");
|
||
|
||
switch (layout) {
|
||
case 'table_only':
|
||
col_table.classList.add("col-12");
|
||
col_map.classList.add("d-none");
|
||
layoutBtnTable.classList.add("text-primary");
|
||
break;
|
||
case 'map_only':
|
||
col_map.classList.add("col-12");
|
||
col_table.classList.add("d-none");
|
||
layoutBtnMap.classList.add("text-primary");
|
||
break;
|
||
case 'both':
|
||
default:
|
||
col_table.classList.add("col-md-6");
|
||
col_map.classList.add("col-md-6");
|
||
layoutBtnBoth.classList.add("text-primary");
|
||
break;
|
||
}
|
||
|
||
// Refresh tables
|
||
$('#locations-table').bootstrapTable('resetView');
|
||
$('#assets-table').bootstrapTable('resetView');
|
||
|
||
// Resize map if visible
|
||
if (layout !== 'table_only' && typeof map !== 'undefined') {
|
||
setTimeout(() => map.resize(), 100);
|
||
}
|
||
|
||
// Save preference
|
||
if (savePreference) {
|
||
saveAssetLayoutPreference(layout);
|
||
}
|
||
}
|
||
|
||
// Layout button event handlers
|
||
layoutBtnTable.addEventListener("click", function() {
|
||
applyAssetLayout('table_only');
|
||
});
|
||
|
||
layoutBtnBoth.addEventListener("click", function() {
|
||
applyAssetLayout('both');
|
||
});
|
||
|
||
layoutBtnMap.addEventListener("click", function() {
|
||
applyAssetLayout('map_only');
|
||
});
|
||
|
||
// Initialize map resize on page load if visible
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
if (currentAssetLayout !== 'table_only' && typeof map !== 'undefined') {
|
||
setTimeout(() => map.resize(), 50);
|
||
}
|
||
});
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
// Totaux //
|
||
// ------ //
|
||
const btnTotals = document.getElementById('totals_button');
|
||
const overlay = document.getElementById('totalsOverlay');
|
||
const closeBtn = document.getElementById('totalsClose');
|
||
const contentEl = document.getElementById('totalsContent');
|
||
|
||
function getAssetsFromSource() {
|
||
const $table = $('#assets-table');
|
||
if (!$table.length || typeof $table.bootstrapTable !== 'function') return [];
|
||
|
||
// Récupère toutes les données en mémoire (toutes pages, filtrées côté client)
|
||
const all = $table.bootstrapTable('getData', {
|
||
useCurrentPage: false,
|
||
includeHiddenRows: false
|
||
}) || [];
|
||
|
||
// Si la sélection custom contient des clés -> on filtre dessus
|
||
if (window.selected_assets_set && selected_assets_set.size > 0) {
|
||
const out = [];
|
||
for (const row of all) {
|
||
// on reconstruit la clé exactement comme pour l'ensemble
|
||
const key = assetKey(row.asset_type, row.id);
|
||
if (selected_assets_set.has(String(key))) out.push(row);
|
||
}
|
||
// Si rien ne matche (ex: décalage de filtre), on retombe sur "all"
|
||
return out.length ? out : all;
|
||
}
|
||
|
||
// Sinon: pas de sélection => on prend tout ce qui est filtré/chargé
|
||
return all;
|
||
}
|
||
|
||
// Regroupe par catégorie, puis par modèle
|
||
function groupByCategoryAndModel(assets) {
|
||
const byCat = {};
|
||
for (const a of assets) {
|
||
const cat = a.category || gettext('Sans catégorie');
|
||
const model = a.model || '—';
|
||
byCat[cat] ??= {};
|
||
byCat[cat][model] ??= { count: 0, length_m: 0, area_m2: 0 };
|
||
byCat[cat][model].count += 1;
|
||
byCat[cat][model].length_m += Number(a.length) || 0;
|
||
byCat[cat][model].area_m2 += Number(a.area) || 0;
|
||
}
|
||
return byCat;
|
||
}
|
||
|
||
function fmtInt(n){ return (Math.round(n)||'-').toLocaleString(); }
|
||
|
||
function renderTotals() {
|
||
const assets = getAssetsFromSource();
|
||
const grouped = groupByCategoryAndModel(assets);
|
||
|
||
const isCustomSelection = window.selected_assets_set && selected_assets_set.size > 0;
|
||
|
||
// En-tête récap global
|
||
const headerCount = interpolate(
|
||
ngettext('%(count)s élément pris en compte',
|
||
'%(count)s éléments pris en compte',
|
||
assets.length),
|
||
{ count: assets.length },
|
||
true
|
||
);
|
||
|
||
const selectionSuffix = isCustomSelection
|
||
? gettext('(sélection)')
|
||
: gettext('(table)');
|
||
|
||
let html = `
|
||
<div class="mb-3 text-muted">
|
||
${headerCount} ${selectionSuffix}
|
||
</div>
|
||
`;
|
||
|
||
// Pour chaque catégorie: un tableau
|
||
const categories = Object.keys(grouped).sort((a,b)=>a.localeCompare(b, 'fr'));
|
||
if (!categories.length) {
|
||
contentEl.innerHTML = `<div class="text-center text-muted">${gettext('Aucune donnée disponible')}</div>`;
|
||
return;
|
||
}
|
||
|
||
for (const cat of categories) {
|
||
const models = grouped[cat];
|
||
const modelNames = Object.keys(models).sort((a,b)=>a.localeCompare(b, 'fr'));
|
||
|
||
// Lignes du body (par modèle)
|
||
const bodyRows = modelNames.map(m => {
|
||
const g = models[m];
|
||
return `
|
||
<tr>
|
||
<td>${m}</td>
|
||
<td class="text-end">${fmtInt(g.count)}</td>
|
||
<td class="text-end">${fmtInt(g.length_m)}</td>
|
||
<td class="text-end">${fmtInt(g.area_m2)}</td>
|
||
</tr>
|
||
`;
|
||
}).join('');
|
||
|
||
// Totaux par catégorie
|
||
const catTotals = modelNames.reduce((acc, m) => {
|
||
acc.count += models[m].count;
|
||
acc.length_m += models[m].length_m;
|
||
acc.area_m2 += models[m].area_m2;
|
||
return acc;
|
||
}, {count:0, length_m:0, area_m2:0});
|
||
|
||
html += `
|
||
<div class="mb-3 pb-2 border-bottom">
|
||
<h6 class="mb-2 d-flex align-items-center gap-2">
|
||
<span class="badge text-bg-secondary">${cat}</span>
|
||
<small class="text-muted">(${fmtInt(catTotals.count)} ${gettext('au total')})</small>
|
||
</h6>
|
||
<div class="table-responsive">
|
||
<table class="table table-sm align-middle">
|
||
<thead>
|
||
<tr>
|
||
<th>${gettext('Modèle')}</th>
|
||
<th class="text-end">${gettext('Nombre')}</th>
|
||
<th class="text-end">${gettext('Longueur (m)')}</th>
|
||
<th class="text-end">${gettext('Surface (m²)')}</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>${bodyRows || `
|
||
<tr><td colspan="4" class="text-center text-muted">${gettext('Aucun modèle')}</td></tr>
|
||
`}</tbody>
|
||
<tfoot>
|
||
<tr class="fw-semibold">
|
||
<td>${gettext('Total catégorie')}</td>
|
||
<td class="text-end">${fmtInt(catTotals.count)}</td>
|
||
<td class="text-end">${fmtInt(catTotals.length_m)}</td>
|
||
<td class="text-end">${fmtInt(catTotals.area_m2)}</td>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
contentEl.innerHTML = html;
|
||
}
|
||
|
||
|
||
// Ouvrir
|
||
btnTotals.addEventListener('click', () => {
|
||
renderTotals();
|
||
overlay.classList.add('show');
|
||
// Accessibilité: focus sur le bouton "Fermer"
|
||
setTimeout(() => closeBtn.focus(), 0);
|
||
});
|
||
|
||
// Fermer
|
||
function hideOverlay() { overlay.classList.remove('show'); }
|
||
closeBtn.addEventListener('click', hideOverlay);
|
||
|
||
// ESC pour fermer
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Escape' && overlay.classList.contains('show')) hideOverlay();
|
||
});
|
||
|
||
// Option: cliquer en dehors du contenu pour fermer (si tu ajoutes un wrapper)
|
||
// overlay.addEventListener('click', (e) => {
|
||
// if (e.target === overlay) hideOverlay();
|
||
// });
|
||
|
||
|
||
// ---------------------
|
||
// Widget de recherche assets
|
||
// ---------------------
|
||
document.addEventListener('DOMContentLoaded', function () {
|
||
const searchInput = document.getElementById('assets-search-input');
|
||
const searchBtn = document.getElementById('assets-search-btn');
|
||
const searchClear = document.getElementById('assets-search-clear');
|
||
|
||
if (!searchInput) return;
|
||
|
||
function runAssetsSearch() {
|
||
const hasVal = searchInput.value.trim() !== '';
|
||
searchClear.style.visibility = hasVal ? 'visible' : 'hidden';
|
||
updateLocations();
|
||
updateAssets();
|
||
}
|
||
|
||
searchBtn.addEventListener('click', runAssetsSearch);
|
||
|
||
searchInput.addEventListener('keydown', function (e) {
|
||
if (e.key === 'Enter') { e.preventDefault(); runAssetsSearch(); }
|
||
else if (e.key === 'Escape') { searchInput.value = ''; runAssetsSearch(); }
|
||
});
|
||
|
||
searchClear.addEventListener('click', function () {
|
||
searchInput.value = '';
|
||
searchClear.style.visibility = 'hidden';
|
||
updateLocations();
|
||
updateAssets();
|
||
});
|
||
|
||
// Reset filters also clears search
|
||
const resetLocBtn = document.getElementById('reset-filters');
|
||
const resetAssBtn = document.getElementById('reset-assets-filters');
|
||
if (resetLocBtn) {
|
||
resetLocBtn.addEventListener('click', function () {
|
||
searchInput.value = '';
|
||
searchClear.style.visibility = 'hidden';
|
||
});
|
||
}
|
||
if (resetAssBtn) {
|
||
resetAssBtn.addEventListener('click', function () {
|
||
searchInput.value = '';
|
||
searchClear.style.visibility = 'hidden';
|
||
});
|
||
}
|
||
});
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
function getCookie(name) {
|
||
let cookieValue = null;
|
||
if (document.cookie && document.cookie !== '') {
|
||
const cookies = document.cookie.split(';');
|
||
for (let i = 0; i < cookies.length; i++) {
|
||
const cookie = cookies[i].trim();
|
||
// Does this cookie string begin with the name we want?
|
||
if (cookie.substring(0, name.length + 1) === (name + '=')) {
|
||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
return cookieValue;
|
||
}
|
||
const csrftoken = getCookie('csrftoken'); |