fix(thematics, home): mise à jour des thématiques (Sécurité, Stationnement, Propreté) et sécurisation du chargement de la carte sur l'accueil
This commit is contained in:
parent
bbaa092550
commit
aad07929b9
5 changed files with 620 additions and 610 deletions
|
|
@ -9,7 +9,7 @@ def create_parking_thematic(apps, schema_editor):
|
||||||
thematic, _created = Thematic.objects.get_or_create(
|
thematic, _created = Thematic.objects.get_or_create(
|
||||||
code='parking',
|
code='parking',
|
||||||
defaults={
|
defaults={
|
||||||
'name_fr': 'Stationnement & Mobilité',
|
'name_fr': 'Stationnement',
|
||||||
'name_nl': 'Parkeren & Mobiliteit',
|
'name_nl': 'Parkeren & Mobiliteit',
|
||||||
'description': 'Thématique dédiée aux emplacements de stationnement et zones de mobilité (vélos, trottinettes, PMR, livraison)',
|
'description': 'Thématique dédiée aux emplacements de stationnement et zones de mobilité (vélos, trottinettes, PMR, livraison)',
|
||||||
'icon': 'bi-p-square',
|
'icon': 'bi-p-square',
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ def create_bike_parking_category(apps, schema_editor):
|
||||||
thematic, _ = Thematic.objects.get_or_create(
|
thematic, _ = Thematic.objects.get_or_create(
|
||||||
code='parking',
|
code='parking',
|
||||||
defaults={
|
defaults={
|
||||||
'name_fr': 'Stationnement & Mobilité',
|
'name_fr': 'Stationnement',
|
||||||
'name_nl': 'Parkeren & Mobiliteit',
|
'name_nl': 'Parkeren & Mobiliteit',
|
||||||
'description': 'Thématique dédiée aux emplacements de stationnement et zones de mobilité (vélos, trottinettes, PMR, livraison)',
|
'description': 'Thématique dédiée aux emplacements de stationnement et zones de mobilité (vélos, trottinettes, PMR, livraison)',
|
||||||
'icon': 'bi-p-square',
|
'icon': 'bi-p-square',
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ MAX_ASSET_FEATURES = 5000
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def parking_index(request):
|
def parking_index(request):
|
||||||
"""Page d'accueil de la thématique Stationnement & Mobilité."""
|
"""Page d'accueil de la thématique Stationnement."""
|
||||||
check_thematic_access(request.user, 'parking')
|
check_thematic_access(request.user, 'parking')
|
||||||
|
|
||||||
parking_thematic = Thematic.objects.filter(code='parking').first()
|
parking_thematic = Thematic.objects.filter(code='parking').first()
|
||||||
|
|
|
||||||
|
|
@ -121,9 +121,9 @@
|
||||||
"pk": 11,
|
"pk": 11,
|
||||||
"fields": {
|
"fields": {
|
||||||
"code": "dpv_micro_projects_security",
|
"code": "dpv_micro_projects_security",
|
||||||
"name_fr": "DPV Micro-projets Sécurité",
|
"name_fr": "Sécurité",
|
||||||
"name_nl": "DPV Micro-projecten Beveiliging",
|
"name_nl": "Veiligheid",
|
||||||
"description": "",
|
"description": "Gestion de la sécurité publique et des équipements associés",
|
||||||
"icon": "bi-exclamation-diamond",
|
"icon": "bi-exclamation-diamond",
|
||||||
"color": "#000000",
|
"color": "#000000",
|
||||||
"uses_occupations": false
|
"uses_occupations": false
|
||||||
|
|
@ -142,6 +142,32 @@
|
||||||
"uses_occupations": false
|
"uses_occupations": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"model": "common.thematic",
|
||||||
|
"pk": 13,
|
||||||
|
"fields": {
|
||||||
|
"code": "parking",
|
||||||
|
"name_fr": "Stationnement",
|
||||||
|
"name_nl": "Parkeren",
|
||||||
|
"description": "Thématique dédiée aux emplacements de stationnement et zones de mobilité (vélos, trottinettes, PMR, livraison)",
|
||||||
|
"icon": "bi-p-square",
|
||||||
|
"color": "#0d6efd",
|
||||||
|
"uses_occupations": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "common.thematic",
|
||||||
|
"pk": 14,
|
||||||
|
"fields": {
|
||||||
|
"code": "clean",
|
||||||
|
"name_fr": "Propreté",
|
||||||
|
"name_nl": "Netheid",
|
||||||
|
"description": "Gestion de la propreté publique, corbeilles et conteneurs",
|
||||||
|
"icon": "bi-trash3",
|
||||||
|
"color": "#198754",
|
||||||
|
"uses_occupations": false
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"model": "common.role",
|
"model": "common.role",
|
||||||
"pk": 1,
|
"pk": 1,
|
||||||
|
|
|
||||||
|
|
@ -1,86 +1,3 @@
|
||||||
|
|
||||||
const map = new maplibregl.Map({
|
|
||||||
container: "map",
|
|
||||||
style: window.MAP_STYLE_LIGHT,
|
|
||||||
//style: 'https://tiles.openfreemap.org/styles/positron',
|
|
||||||
center: [4.3572, 50.8477],
|
|
||||||
zoom: 11,
|
|
||||||
minzoom: 0,
|
|
||||||
maxzoom: 24
|
|
||||||
});
|
|
||||||
|
|
||||||
async function fetchAndMergeGeoJSON(urls) {
|
|
||||||
const allFeatures = [];
|
|
||||||
|
|
||||||
for (const url of urls) {
|
|
||||||
const response = await fetch(url);
|
|
||||||
const data = await response.json();
|
|
||||||
if (data && data.features) {
|
|
||||||
allFeatures.push(...data.features);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: 'FeatureCollection',
|
|
||||||
features: allFeatures
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
map.on('load', async () => {
|
|
||||||
const mergedGeoJSON = await fetchAndMergeGeoJSON(interventions_geojson_urls);
|
|
||||||
|
|
||||||
map.addSource('interventions', {
|
|
||||||
'type': 'geojson',
|
|
||||||
'data': mergedGeoJSON
|
|
||||||
});
|
|
||||||
|
|
||||||
// map.addLayer({
|
|
||||||
// id: 'heatmap',
|
|
||||||
// type: 'heatmap',
|
|
||||||
// source: 'interventions',
|
|
||||||
// paint: {
|
|
||||||
// 'heatmap-weight': ['get', 'density'],
|
|
||||||
// 'heatmap-intensity': 1,
|
|
||||||
// 'heatmap-color': [
|
|
||||||
// 'interpolate',
|
|
||||||
// ['linear'],
|
|
||||||
// ['heatmap-density'],
|
|
||||||
// 0, 'rgba(33,102,172,0)',
|
|
||||||
// 0.2, 'rgb(103,169,207)',
|
|
||||||
// 0.4, 'rgb(209,229,240)',
|
|
||||||
// 0.6, 'rgb(253,219,199)',
|
|
||||||
// 0.8, 'rgb(239,138,98)',
|
|
||||||
// 1, 'rgb(178,24,43)'
|
|
||||||
// ],
|
|
||||||
// 'heatmap-radius': 15,
|
|
||||||
// 'heatmap-opacity': 0.75
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
map.addLayer({
|
|
||||||
'id': 'interventions',
|
|
||||||
'type': 'circle',
|
|
||||||
'source': 'interventions',
|
|
||||||
//filter: ['has', 'point_count'],
|
|
||||||
minzoom: 0,
|
|
||||||
maxzoom: 24,
|
|
||||||
'paint': {
|
|
||||||
'circle-radius': [
|
|
||||||
'interpolate',
|
|
||||||
['linear'],
|
|
||||||
['zoom'],
|
|
||||||
10, 5,
|
|
||||||
22, 15
|
|
||||||
],
|
|
||||||
//'circle-color': '#0094ff',
|
|
||||||
'circle-color': INTERVENTION_STATUS_COLOR_EXPR,
|
|
||||||
'circle-opacity': 0.7
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
function statusFormatter(value) {
|
function statusFormatter(value) {
|
||||||
const icons = {
|
const icons = {
|
||||||
'in_preparation': { label: gettext('En préparation'), icon: 'bi-pencil', color: 'secondary' },
|
'in_preparation': { label: gettext('En préparation'), icon: 'bi-pencil', color: 'secondary' },
|
||||||
|
|
@ -97,7 +14,6 @@ function statusFormatter(value) {
|
||||||
'validated': { label: gettext('Validé'), icon: 'bi-award', color: 'success' },
|
'validated': { label: gettext('Validé'), icon: 'bi-award', color: 'success' },
|
||||||
'invoiced': { label: gettext('Comptabilisé'), icon: 'bi-receipt', color: 'dark' },
|
'invoiced': { label: gettext('Comptabilisé'), icon: 'bi-receipt', color: 'dark' },
|
||||||
'closed': { label: gettext('Finalisé'), icon: 'bi-archive', color: 'dark' },
|
'closed': { label: gettext('Finalisé'), icon: 'bi-archive', color: 'dark' },
|
||||||
// 'archived': { label: gettext('Archivé'), icon: 'bi-box-seam', color: 'dark' },
|
|
||||||
'canceled': { label: gettext('Annulé'), icon: 'bi-x-circle', color: 'secondary' },
|
'canceled': { label: gettext('Annulé'), icon: 'bi-x-circle', color: 'secondary' },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -107,8 +23,71 @@ function statusFormatter(value) {
|
||||||
return `<span class="badge text-bg-${entry.color} status-${value}"><i class="bi ${entry.icon} me-2"></i>${entry.label}</span>`;
|
return `<span class="badge text-bg-${entry.color} status-${value}"><i class="bi ${entry.icon} me-2"></i>${entry.label}</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapContainer = document.getElementById("map");
|
||||||
|
|
||||||
|
if (mapContainer && typeof maplibregl !== 'undefined') {
|
||||||
|
const map = new maplibregl.Map({
|
||||||
|
container: mapContainer,
|
||||||
|
style: window.MAP_STYLE_LIGHT,
|
||||||
|
center: [4.3572, 50.8477],
|
||||||
|
zoom: 11,
|
||||||
|
minzoom: 0,
|
||||||
|
maxzoom: 24
|
||||||
|
});
|
||||||
|
|
||||||
|
async function fetchAndMergeGeoJSON(urls) {
|
||||||
|
const allFeatures = [];
|
||||||
|
|
||||||
|
for (const url of urls) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
const data = await response.json();
|
||||||
|
if (data && data.features) {
|
||||||
|
allFeatures.push(...data.features);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Error fetching geojson:', url, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'FeatureCollection',
|
||||||
|
features: allFeatures
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
map.on('load', async () => {
|
||||||
|
if (typeof interventions_geojson_urls !== 'undefined' && interventions_geojson_urls.length > 0) {
|
||||||
|
const mergedGeoJSON = await fetchAndMergeGeoJSON(interventions_geojson_urls);
|
||||||
|
|
||||||
|
map.addSource('interventions', {
|
||||||
|
'type': 'geojson',
|
||||||
|
'data': mergedGeoJSON
|
||||||
|
});
|
||||||
|
|
||||||
|
map.addLayer({
|
||||||
|
'id': 'interventions',
|
||||||
|
'type': 'circle',
|
||||||
|
'source': 'interventions',
|
||||||
|
minzoom: 0,
|
||||||
|
maxzoom: 24,
|
||||||
|
'paint': {
|
||||||
|
'circle-radius': [
|
||||||
|
'interpolate',
|
||||||
|
['linear'],
|
||||||
|
['zoom'],
|
||||||
|
10, 5,
|
||||||
|
22, 15
|
||||||
|
],
|
||||||
|
'circle-color': typeof INTERVENTION_STATUS_COLOR_EXPR !== 'undefined' ? INTERVENTION_STATUS_COLOR_EXPR : '#0094ff',
|
||||||
|
'circle-opacity': 0.7
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
map.on('click', function(e) {
|
map.on('click', function(e) {
|
||||||
const radiusInPixels = 10; // Rayon autour du clic
|
const radiusInPixels = 10;
|
||||||
|
|
||||||
const features = map.queryRenderedFeatures([
|
const features = map.queryRenderedFeatures([
|
||||||
[e.point.x - radiusInPixels, e.point.y - radiusInPixels],
|
[e.point.x - radiusInPixels, e.point.y - radiusInPixels],
|
||||||
|
|
@ -121,12 +100,14 @@ map.on('click', function(e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Générer le contenu de la popup avec tous les points proches
|
|
||||||
const popupContent = features.map((feature) => {
|
const popupContent = features.map((feature) => {
|
||||||
const props = feature.properties;
|
const props = feature.properties;
|
||||||
|
const detailUrl = (typeof interventionDetailUrlPattern !== 'undefined')
|
||||||
|
? interventionDetailUrlPattern.replace('/0/', '/' + props.id + '/')
|
||||||
|
: '#';
|
||||||
return `
|
return `
|
||||||
<div>
|
<div>
|
||||||
<strong><a href="${interventionDetailUrlPattern.replace('/0/', '/' + props.id + '/')}">${props.code || gettext('Intervention')}</a></strong><br/>
|
<strong><a href="${detailUrl}">${props.code || gettext('Intervention')}</a></strong><br/>
|
||||||
${gettext("Statut")}: ${statusFormatter(props.status)}<br/>
|
${gettext("Statut")}: ${statusFormatter(props.status)}<br/>
|
||||||
${gettext("Titre")}: ${props.title || '-'}
|
${gettext("Titre")}: ${props.title || '-'}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -134,7 +115,6 @@ map.on('click', function(e) {
|
||||||
`;
|
`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
// Afficher la popup à l’endroit du clic
|
|
||||||
new maplibregl.Popup()
|
new maplibregl.Popup()
|
||||||
.setLngLat(e.lngLat)
|
.setLngLat(e.lngLat)
|
||||||
.setHTML(popupContent)
|
.setHTML(popupContent)
|
||||||
|
|
@ -158,49 +138,53 @@ function populateLegend() {
|
||||||
return `<div class="mb-1">${statusFormatter(status)}</div>`;
|
return `<div class="mb-1">${statusFormatter(status)}</div>`;
|
||||||
});
|
});
|
||||||
|
|
||||||
legendContainer.innerHTML = `
|
legendContainer.innerHTML = legendItems.join('');
|
||||||
${legendItems.join('')}
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
document.addEventListener('DOMContentLoaded', populateLegend);
|
document.addEventListener('DOMContentLoaded', populateLegend);
|
||||||
|
} else {
|
||||||
|
populateLegend();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ### chart ###
|
// ### chart ###
|
||||||
|
const chartDom = document.getElementById('chartNbrInterventions');
|
||||||
|
if (chartDom && typeof echarts !== 'undefined' && typeof seriesData !== 'undefined') {
|
||||||
const series = [
|
const series = [
|
||||||
{
|
{
|
||||||
name: gettext('Correctif'),
|
name: gettext('Correctif'),
|
||||||
type: 'line',
|
type: 'line',
|
||||||
smooth: true,
|
smooth: true,
|
||||||
data: seriesData['Correctif'],
|
data: seriesData['Correctif'] || [],
|
||||||
itemStyle: { color: '#dc3545' } // Bootstrap "danger"
|
itemStyle: { color: '#dc3545' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: gettext('Préventif'),
|
name: gettext('Préventif'),
|
||||||
type: 'line',
|
type: 'line',
|
||||||
smooth: true,
|
smooth: true,
|
||||||
data: seriesData['Préventif'],
|
data: seriesData['Préventif'] || [],
|
||||||
itemStyle: { color: '#ffc107' } // Bootstrap "warning"
|
itemStyle: { color: '#ffc107' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: gettext('Amélioratif'),
|
name: gettext('Amélioratif'),
|
||||||
type: 'line',
|
type: 'line',
|
||||||
smooth: true,
|
smooth: true,
|
||||||
data: seriesData['Amélioratif'],
|
data: seriesData['Amélioratif'] || [],
|
||||||
itemStyle: { color: '#28a745' } // Bootstrap "success"
|
itemStyle: { color: '#28a745' }
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
legend: { data: ['Correctif', 'Préventif', 'Amélioratif'] },
|
legend: { data: [gettext('Correctif'), gettext('Préventif'), gettext('Amélioratif')] },
|
||||||
tooltip: { trigger: 'axis' },
|
tooltip: { trigger: 'axis' },
|
||||||
xAxis: { type: 'category', data: seriesData.dates },
|
xAxis: { type: 'category', data: seriesData.dates || [] },
|
||||||
yAxis: { type: 'value' },
|
yAxis: { type: 'value' },
|
||||||
series: series
|
series: series
|
||||||
};
|
};
|
||||||
|
|
||||||
const chartDom = document.getElementById('chartNbrInterventions');
|
|
||||||
const myChart = echarts.init(chartDom);
|
const myChart = echarts.init(chartDom);
|
||||||
myChart.setOption(option);
|
myChart.setOption(option);
|
||||||
|
|
||||||
window.addEventListener('resize', () => myChart.resize());
|
window.addEventListener('resize', () => myChart.resize());
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue