529 lines
No EOL
18 KiB
JavaScript
529 lines
No EOL
18 KiB
JavaScript
|
|
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 = `<button class="btn btn-sm btn-light detail-btn" data-id="${row.id}" data-bs-toggle="tooltip" title="Detail">
|
|
<i class="bi bi-info-circle"></i>
|
|
</button>`;
|
|
|
|
if (row.lon) {
|
|
resp = resp +
|
|
`<button class="btn btn-sm btn-light zoom-btn" 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 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 `<span class="badge text-bg-${entry.color}"><i class="bi ${entry.icon} me-2"></i>${entry.label}</span>`;
|
|
}
|
|
|
|
|
|
// ### 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);
|
|
}
|
|
}); |