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);
}
});