var projects_geojson = {}; const projectsgeojsonloader = new GeojsonLoader(); const projects_layer_defs = [ { 'id': 'projects', 'type': 'circle', 'source': 'projects', //filter: ['has', 'point_count'], minzoom: 0, maxzoom: 24, 'paint': { 'circle-radius': [ 'interpolate', ['linear'], ['zoom'], 10, 2, 22, 15 ], //'circle-color': '#0094ff', 'circle-color': [ 'match', ['get', 'status'], 'in_preparation', '#6c757d', // secondary 'planned', '#ffc107', // warning 'in_progress', '#007bff', // primary 'finished', '#17a2b8', // info 'invoiced', '#343a40', // dark 'closed', '#343a40', // dark 'canceled', '#6c757d', // secondary '#888888' // default si statut inconnu ], 'circle-opacity': 0.7 } } // { // id: 'projects-cluster-count', // type: 'symbol', // source: 'projects', // //filter: ['has', 'point_count'], // minzoom: 0, // maxzoom: 24, // layout: { // 'text-field': [ // 'case', // ['has', 'point_count_abbreviated'], // Si la propriété 'point_count_abbreviated' existe // ['get', 'point_count_abbreviated'], // Afficher la valeur de 'point_count_abbreviated' // '1' // Sinon, afficher '1' // ], // 'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'], // 'text-size': 12 // } // } ] handleGeojsonMapLoading(init=true, projectsgeojsonloader, 'projects', projects_layer_defs, cluster=true); // Load Map maploader.loadMap("map"); // Load initial data const params = new URLSearchParams(window.location.search).toString(); projectsgeojsonloader.loadGeojson(projectsgeojsonUrl+`?${params}`); // Reset table if error occurs when loading GeoJSON projectsgeojsonloader.addEventListener("geojsonError", event => { loadDataIntoprojectsTable([]); }); // Load data into table when GeoJSON is loaded projectsgeojsonloader.addEventListener("geojsonLoaded", event => { var geojson_data = event.detail; loadDataIntoprojectsTable(geojson_data.features); projects_geojson = geojson_data; }); // Apply the correct width to the dropdown-menu document.addEventListener("DOMContentLoaded", function () { const parent_col = $('#col-left')[0]; const parentWidth = Math.max(550, parent_col.offsetWidth - 40); $('.filter-dropdown').width(`${parentWidth}px`); }); // Apply filters document.getElementById('apply-filters').addEventListener('click', function(e) { const beginDate = document.getElementById('begin_date').value; const endDate = document.getElementById('end_date').value; if (beginDate && endDate && new Date(beginDate) > new Date(endDate)) { alert(gettext('La date de début ne peut pas être après la date de fin.')); return; } 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() !== '') { // Check that the field is not empty params.append(key, value); } } // Update URL const newUrl = `${window.location.pathname}?${params.toString()}`; window.history.pushState({}, '', newUrl); // Load filtered data handleGeojsonMapLoading(init=false, projectsgeojsonloader, 'projects'); projectsgeojsonloader.loadGeojson(projectsgeojsonUrl+`?${params}`); // Save preferences savePreferencesFromForm(params); // 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 // Uncheck all filter checkboxes form.querySelectorAll('input[type="checkbox"][name="status"]').forEach(cb => { cb.checked = false; }); form.querySelectorAll('input[type="checkbox"][name="thematic"]').forEach(cb => { cb.checked = false; }); form.querySelectorAll('input[type="checkbox"][name="tag"]').forEach(cb => { cb.checked = false; }); // Explicitly clear date fields document.getElementById('begin_date').value = ''; document.getElementById('end_date').value = ''; // Remove URL parameters window.history.pushState({}, '', window.location.pathname); // Reload all data without filters handleGeojsonMapLoading(init=false, projectsgeojsonloader, 'projects'); projectsgeojsonloader.loadGeojson(projectsgeojsonUrl); // Save empty preferences on reset if (typeof savePreferencesUrl !== 'undefined') { fetch(savePreferencesUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, body: JSON.stringify({ filters: {}, sort: {} }) }).catch(() => {}); } // 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, projects_geojson, loadDataIntoprojectsTable); }); // Loads data into table function loadDataIntoprojectsTable(features) { const tableData = features.map((feature) => { return { actions: '', // Column for action icons, manipulated by the formatter id: feature.id, code: feature.properties.code, title: feature.properties.title, begin_time: feature.properties.begin_time, status: feature.properties.status, lon: feature.geometry && feature.geometry.coordinates ? feature.geometry.coordinates[0] : null, // Longitude lat: feature.geometry && feature.geometry.coordinates ? feature.geometry.coordinates[1] : null // Latitude }; }); $('#projects-spinner').hide(); $('#projects-table-container').show(); $('#projects-table').bootstrapTable('load', tableData); } // Refresh table view when switching tabs $('#projects-table-tab').on('click', function () { $('#projects-table').bootstrapTable('resetView'); }); // Add action buttons for each row of the table function actionsFormatter(value, row, index) { resp = ``; if (row.lon) { resp = resp + ` `; } return resp; } function statusFormatter(value, row) { const icons = { 'in_preparation': { label: gettext('En prépa'), icon: 'bi-pencil', color: 'secondary' }, 'planned': { label: gettext('À planifier'), icon: 'bi-calendar-plus', color: 'warning' }, 'in_progress': { label: gettext('En cours'), icon: 'bi-play-circle', color: 'primary' }, 'finished': { label: gettext('Terminé'), icon: 'bi-check-circle', color: 'info' }, 'invoiced': { label: gettext('Comptabilisé'), icon: 'bi-receipt', color: 'dark' }, 'closed': { label: gettext('Finalisé'), icon: 'bi-archive', color: 'dark' }, 'canceled': { label: gettext('Annulé'), icon: 'bi-x-circle', color: 'secondary' }, }; const entry = icons[value]; if (!entry) return value; return `${entry.label}`; } // ### Actions on the map ### // // -----------------------------// map.on('click', 'projects-clusters', async (e) => { const currentZoom = map.getZoom(); const features = map.queryRenderedFeatures(e.point, { layers: ['projects-clusters'] }); const center = features[0].geometry.coordinates; if (currentZoom > 15) { const { lng, lat } = e.lngLat; // Clicked coordinates const distance = 10; // Distance in meters for each side // Convert distance to geographic degrees const latOffset = distance / 111320; // 1° latitude = ~111,320 m const lngOffset = distance / (111320 * Math.cos((lat * Math.PI) / 180)); const sw = new maplibregl.LngLat(lng - lngOffset, lat - latOffset); const ne = new maplibregl.LngLat(lng + lngOffset, lat + latOffset); const bounds = new maplibregl.LngLatBounds(sw, ne); // Filter table data based on area filterTableByMapBounds(map, projects_geojson, loadDataIntoprojectsTable); } else { const zoom = currentZoom + 2 map.easeTo({ center: center, zoom }); } }); // Mouse pointer on clusters map.on('mouseenter', 'projects-clusters', () => { map.getCanvas().style.cursor = 'pointer'; }); map.on('mouseleave', 'projects-clusters', () => { map.getCanvas().style.cursor = ''; }); // Initialization after page load $( document ).ready(function() { function calculateTableHeight() { const windowHeight = $(window).height(); // Total window height const offsetTop = 285; // Y position of the table const footerHeight = $('.footer').outerHeight() || 0; // If footer return windowHeight - offsetTop - footerHeight - 20; // Adjustment with padding } let initialSortName = undefined; let initialSortOrder = 'asc'; if (typeof savedSort !== 'undefined' && savedSort.sortName) { initialSortName = savedSort.sortName; initialSortOrder = savedSort.sortOrder || 'asc'; } $('#projects-table').bootstrapTable({ columns: [ { field: 'actions', title: '', formatter:'actionsFormatter', width:'100' }, { field: 'code', title: gettext('Code'), sortable: true }, { field: 'title', title: gettext('Titre'), sortable: true }, { field: 'begin_time', title: gettext('Début'), formatter: 'dateFormatter', sortable: true}, { field: 'status', title: gettext('Statut'), formatter: 'statusFormatter', class: 'text-nowrap', sortable: true } ], sortName: initialSortName, sortOrder: initialSortOrder, height: calculateTableHeight() // Apply the calculated height }); // Register actions to the table registerDetailAction('#projects-table', '.detail-btn', projectsurl); registerZoomAction('#projects-table', '.zoom-btn', map, 17); // Save preferences on sort changes $('#projects-table').on('sort.bs.table', function (e, name, order) { setTimeout(savePreferencesFromUrl, 500); }); // Select-all / deselect-all buttons document.querySelectorAll('.select-all-btn').forEach(btn => { btn.addEventListener('click', function(e) { e.preventDefault(); const field = this.getAttribute('data-field'); const container = document.querySelector(`.filter-options[data-field="${field}"]`); if (container) { container.querySelectorAll('input[type="checkbox"]').forEach(cb => { cb.checked = true; }); } }); }); document.querySelectorAll('.deselect-all-btn').forEach(btn => { btn.addEventListener('click', function(e) { e.preventDefault(); const field = this.getAttribute('data-field'); const container = document.querySelector(`.filter-options[data-field="${field}"]`); if (container) { container.querySelectorAll('input[type="checkbox"]').forEach(cb => { cb.checked = false; }); } }); }); }); const col_left = document.getElementById("col-left"); const col_right = document.getElementById("col-right"); const col_btnLeft = document.getElementById("expand-col-left"); const col_iconLeft = col_btnLeft.querySelector("i"); const col_btnRight = document.getElementById("expand-col-right"); const col_iconRight = col_btnRight.querySelector("i"); // Fonction pour reset les deux colonnes function col_resetColumns() { col_left.classList.remove("col-11", "order-2"); col_right.classList.remove("col-11", "order-2"); col_left.classList.add("col-md-6"); col_right.classList.add("col-md-6"); col_btnLeft.setAttribute("aria-expanded", "false"); col_btnRight.setAttribute("aria-expanded", "false"); col_iconLeft.classList.remove("bi-box-arrow-in-left"); col_iconLeft.classList.add("bi-box-arrow-in-right"); col_iconRight.classList.remove("bi-box-arrow-in-right"); col_iconRight.classList.add("bi-box-arrow-in-left"); $('#projects-table').bootstrapTable('resetView'); } // Bouton gauche col_btnLeft.addEventListener("click", function () { const isExpanded = col_btnLeft.getAttribute("aria-expanded") === "true"; if (!isExpanded) { // Étendre la gauche col_left.classList.remove("col-md-6", "order-2"); col_right.classList.remove("col-md-6", "order-2"); col_left.classList.add("col-11"); col_right.classList.add("col-11", "order-2"); // Mettre l'état des boutons col_btnLeft.setAttribute("aria-expanded", "true"); col_btnRight.setAttribute("aria-expanded", "false"); // Mettre à jour les icônes col_iconLeft.classList.remove("bi-box-arrow-in-right"); col_iconLeft.classList.add("bi-box-arrow-in-left"); col_iconRight.classList.remove("bi-box-arrow-in-right"); col_iconRight.classList.add("bi-box-arrow-in-left"); $('#projects-table').bootstrapTable('resetView'); } else { col_resetColumns(); } }); // Bouton droite col_btnRight.addEventListener("click", function () { const isExpanded = col_btnRight.getAttribute("aria-expanded") === "true"; if (!isExpanded) { // Étendre la droite col_left.classList.remove("col-md-6", "order-2"); col_right.classList.remove("col-md-6", "order-2"); col_right.classList.add("col-11"); col_left.classList.add("col-11", "order-2"); // Mettre l'état des boutons col_btnRight.setAttribute("aria-expanded", "true"); col_btnLeft.setAttribute("aria-expanded", "false"); // Mettre à jour les icônes col_iconRight.classList.remove("bi-box-arrow-in-left"); col_iconRight.classList.add("bi-box-arrow-in-right"); col_iconLeft.classList.remove("bi-box-arrow-in-left"); col_iconLeft.classList.add("bi-box-arrow-in-right"); $('#projects-table').bootstrapTable('resetView'); } else { col_resetColumns(); } }); // ==================== Preferences Management ==================== 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(); if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } function savePreferencesFromUrl() { if (typeof savePreferencesUrl === 'undefined') return; const urlParams = new URLSearchParams(window.location.search); const filters = {}; const filterParams = ['status', 'tag', 'begin_date', 'end_date', 'thematic']; filterParams.forEach((param) => { const values = urlParams.getAll(param); if (values.length > 1) { filters[param] = values; } else if (values.length === 1 && values[0] !== '') { filters[param] = values[0]; } }); const $table = $('#projects-table'); let sort = {}; if ($table.length && $table.data('bootstrap.table')) { const options = $table.bootstrapTable('getOptions'); if (options.sortName) { sort = { sortName: options.sortName, sortOrder: options.sortOrder || 'asc' }; } } fetch(savePreferencesUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, body: JSON.stringify({ filters, sort }) }).catch(() => {}); } async function savePreferencesFromForm(urlParams) { if (typeof savePreferencesUrl === 'undefined') return; const filters = {}; const filterParams = ['status', 'tag', 'begin_date', 'end_date', 'thematic']; filterParams.forEach((param) => { const values = urlParams.getAll(param); if (values.length > 1) { filters[param] = values; } else if (values.length === 1 && values[0] !== '') { filters[param] = values[0]; } }); const $table = $('#projects-table'); let sort = {}; if ($table.length && $table.data('bootstrap.table')) { const options = $table.bootstrapTable('getOptions'); if (options.sortName) { sort = { sortName: options.sortName, sortOrder: options.sortOrder || 'asc' }; } } try { await fetch(savePreferencesUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, body: JSON.stringify({ filters, sort }), keepalive: true }); } catch (e) { // Silently fail } } // Save preferences on page load if filters are already present in URL document.addEventListener('DOMContentLoaded', function () { if (window.location.search) { // Debounce to allow table to initialize first setTimeout(savePreferencesFromUrl, 1000); } });