mirror of
https://forge.chapril.org/tykayn/osm-commerces
synced 2025-06-20 01:44:42 +02:00
add history on stats
This commit is contained in:
parent
7fb0c9c8c2
commit
b61fa6a287
11 changed files with 502 additions and 37 deletions
|
@ -24,9 +24,160 @@
|
|||
|
||||
{% block javascripts %}
|
||||
{{ parent() }}
|
||||
<script src="{{ asset('js/maplibre/maplibre-gl.js') }}"></script>
|
||||
<script src="{{ asset('js/turf/turf.min.js') }}"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src='{{ asset('js/maplibre/maplibre-gl.js') }}'></script>
|
||||
<script src="https://unpkg.com/@turf/turf@6/turf.min.js"></script>
|
||||
<script src="{{ asset('js/map-utils.js') }}"></script>
|
||||
<script>
|
||||
// Attendre que le DOM et tous les scripts soient chargés
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Vérifier que Chart.js est disponible
|
||||
if (typeof Chart === 'undefined') {
|
||||
console.error('Chart.js n\'est pas chargé');
|
||||
return;
|
||||
}
|
||||
|
||||
// Vérifier que les fonctions sont disponibles
|
||||
if (typeof calculateCompletion === 'undefined') {
|
||||
console.error('La fonction calculateCompletion n\'est pas définie');
|
||||
return;
|
||||
}
|
||||
|
||||
let map;
|
||||
let dropMarkers = [];
|
||||
let currentMarkerType = 'drop';
|
||||
let completionChart;
|
||||
let contextMenu;
|
||||
let selectedFeature = null;
|
||||
|
||||
// Fonction pour calculer la distribution des taux de complétion
|
||||
function calculateCompletionDistribution(features) {
|
||||
const buckets = Array(11).fill(0); // 0-10%, 11-20%, ..., 91-100%
|
||||
|
||||
features.forEach(feature => {
|
||||
const completion = calculateCompletion(feature.properties);
|
||||
const bucketIndex = Math.min(Math.floor(completion.percentage / 10), 10);
|
||||
buckets[bucketIndex]++;
|
||||
});
|
||||
|
||||
return buckets;
|
||||
}
|
||||
|
||||
// Fonction pour créer le graphique de complétion
|
||||
function createCompletionChart(features) {
|
||||
const ctx = document.getElementById('completionChart').getContext('2d');
|
||||
const distribution = calculateCompletionDistribution(features);
|
||||
|
||||
if (completionChart) {
|
||||
completionChart.destroy();
|
||||
}
|
||||
|
||||
completionChart = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: ['0-10%', '11-20%', '21-30%', '31-40%', '41-50%', '51-60%', '61-70%', '71-80%', '81-90%', '91-100%'],
|
||||
datasets: [{
|
||||
label: 'Nombre de lieux',
|
||||
data: distribution,
|
||||
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
||||
borderColor: 'rgba(75, 192, 192, 1)',
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
stepSize: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Fonction pour charger les lieux depuis l'API Overpass
|
||||
async function loadPlaces() {
|
||||
try {
|
||||
const response = await fetch('https://overpass-api.de/api/interpreter?data={{query_places|raw}}');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.features && data.features.length > 0) {
|
||||
// Mettre à jour les statistiques
|
||||
const totallieux = data.features.length;
|
||||
document.getElementById('totallieux').textContent = totallieux;
|
||||
|
||||
// Calculer et afficher la distribution des taux de complétion
|
||||
createCompletionChart(data.features);
|
||||
|
||||
// Mettre à jour les marqueurs sur la carte
|
||||
dropMarkers = updateMarkers(data.features, map, currentMarkerType, dropMarkers, data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du chargement des lieux:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialisation de la carte
|
||||
map = new maplibregl.Map({
|
||||
container: 'map',
|
||||
style: 'https://api.maptiler.com/maps/basic-v2/style.json?key={{ maptiler_token }}',
|
||||
center: [2.3522, 48.8566], // Paris
|
||||
zoom: 12
|
||||
});
|
||||
|
||||
// Ajouter les contrôles de navigation
|
||||
map.addControl(new maplibregl.NavigationControl());
|
||||
|
||||
// Gestionnaire d'événements pour le menu contextuel
|
||||
map.on('contextmenu', function(e) {
|
||||
e.preventDefault();
|
||||
const features = map.queryRenderedFeatures(e.point, {
|
||||
layers: ['markers', 'circles']
|
||||
});
|
||||
|
||||
if (features.length > 0) {
|
||||
selectedFeature = features[0];
|
||||
const popup = new maplibregl.Popup()
|
||||
.setLngLat(e.lngLat)
|
||||
.setHTML(`
|
||||
<div class="context-menu">
|
||||
<button onclick="window.location.href='{{ path('app_admin_labourer', {'insee_code': 'ZONE_CODE'}) }}'.replace('ZONE_CODE', '${selectedFeature.properties.insee_code}')">
|
||||
Labourer cette zone
|
||||
</button>
|
||||
</div>
|
||||
`)
|
||||
.addTo(map);
|
||||
}
|
||||
});
|
||||
|
||||
// Gestionnaire d'événements pour le clic sur la carte
|
||||
map.on('click', function(e) {
|
||||
const features = map.queryRenderedFeatures(e.point, {
|
||||
layers: ['markers', 'circles']
|
||||
});
|
||||
|
||||
if (features.length > 0) {
|
||||
const popup = new maplibregl.Popup()
|
||||
.setLngLat(e.lngLat)
|
||||
.setHTML(createPopupContent(features[0].properties))
|
||||
.addTo(map);
|
||||
}
|
||||
});
|
||||
|
||||
// Gestionnaire d'événements pour le bouton de changement de type de marqueur
|
||||
document.getElementById('toggleMarkerType').addEventListener('click', function() {
|
||||
currentMarkerType = currentMarkerType === 'drop' ? 'circle' : 'drop';
|
||||
this.textContent = currentMarkerType === 'drop' ? 'Afficher en cercles' : 'Afficher en points';
|
||||
loadPlaces();
|
||||
});
|
||||
|
||||
// Charger les lieux au démarrage
|
||||
loadPlaces();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
@ -60,7 +211,7 @@
|
|||
</div>
|
||||
<div class="col-md-4 col-12">
|
||||
<span class="badge bg-success">
|
||||
<i class="bi bi-pencil-square"></i> {{ stats.getAvecNote() }} / {{ stats.places|length }} commerces avec note
|
||||
<i class="bi bi-pencil-square"></i> {{ stats.getAvecNote() }} / {{ stats.places|length }} lieux avec note
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -75,38 +226,38 @@
|
|||
<div class="col-md-3 col-12">
|
||||
<span class="badge bg-primary">
|
||||
<i class="bi bi-building"></i> {{ stats.places | length}}
|
||||
</span>commerces dans la zone.
|
||||
</span>lieux dans la zone.
|
||||
</div>
|
||||
<div class="col-md-3 col-12">
|
||||
<span class="badge bg-primary">
|
||||
<i class="bi bi-clock"></i> {{ stats.getAvecHoraires() }}
|
||||
</span>
|
||||
commerces avec horaires.
|
||||
lieux avec horaires.
|
||||
</div>
|
||||
<div class="col-md-3 col-12">
|
||||
<span class="badge bg-primary">
|
||||
<i class="bi bi-map"></i> {{ stats.getAvecAdresse() }}
|
||||
</span>
|
||||
commerces avec adresse.
|
||||
lieux avec adresse.
|
||||
</div>
|
||||
<div class="col-md-3 col-12">
|
||||
<span class="badge bg-primary">
|
||||
<i class="bi bi-globe"></i> {{ stats.getAvecSite() }}
|
||||
</span>
|
||||
commerces avec site web renseigné.
|
||||
lieux avec site web renseigné.
|
||||
</div>
|
||||
<div class="col-md-3 col-12">
|
||||
<span class="badge bg-primary">
|
||||
<i class="bi bi-arrow-up-right"></i>
|
||||
{{ stats.getAvecAccessibilite() }}
|
||||
</span>
|
||||
commerces avec accessibilité PMR renseignée.
|
||||
lieux avec accessibilité PMR renseignée.
|
||||
</div>
|
||||
<div class="col-md-3 col-12">
|
||||
<span class="badge bg-primary">
|
||||
<i class="bi bi-chat-dots"></i> {{ stats.getAvecNote() }}
|
||||
</span>
|
||||
commerces avec note renseignée.
|
||||
lieux avec note renseignée.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -134,9 +285,10 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="card mt-4">
|
||||
|
||||
{% include 'admin/stats_history.html.twig' %}
|
||||
<div id="distribution_completion" class="mt-4 mb-4"></div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-12">
|
||||
<h1 class="card-title p-4">Tableau des {{ stats.places |length }} lieux</h1>
|
||||
|
@ -179,6 +331,14 @@
|
|||
<th>Date</th>
|
||||
<th>Places</th>
|
||||
<th>Complétion</th>
|
||||
<th>Emails count</th>
|
||||
<th>Emails sent</th>
|
||||
<th>Opening hours</th>
|
||||
<th>Address</th>
|
||||
<th>Website</th>
|
||||
<th>Siret</th>
|
||||
{# <th>Accessibilite</th> #}
|
||||
{# <th>Note</th> #}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -187,6 +347,14 @@
|
|||
<td>{{ stat.date|date('d/m/Y') }}</td>
|
||||
<td>{{ stat.placesCount }}</td>
|
||||
<td>{{ stat.completionPercent }}%</td>
|
||||
<td>{{ stat.emailsCount }}</td>
|
||||
<td>{{ stat.emailsSent }}</td>
|
||||
<td>{{ stat.openingHoursCount }}</td>
|
||||
<td>{{ stat.addressCount }}</td>
|
||||
<td>{{ stat.websiteCount }}</td>
|
||||
<td>{{ stat.siretCount }}</td>
|
||||
{# <td>{{ stat.accessibiliteCount }}</td> #}
|
||||
{# <td>{{ stat.noteCount }}</td> #}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
@ -255,7 +423,7 @@
|
|||
data: {
|
||||
labels: ['0-10%', '10-20%', '20-30%', '30-40%', '40-50%', '50-60%', '60-70%', '70-80%', '80-90%', '90-100%'],
|
||||
datasets: [{
|
||||
label: 'Nombre de commerces',
|
||||
label: 'Nombre de lieux',
|
||||
data: distribution,
|
||||
backgroundColor: 'rgba(0, 128, 0, 0.1)',
|
||||
borderColor: 'rgba(0, 128, 0, 1)',
|
||||
|
@ -277,7 +445,7 @@
|
|||
beginAtZero: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Nombre de commerces'
|
||||
text: 'Nombre de lieux'
|
||||
}
|
||||
},
|
||||
x: {
|
||||
|
@ -290,7 +458,7 @@
|
|||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Distribution du taux de complétion des commerces'
|
||||
text: 'Distribution du taux de complétion des lieux'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
62
templates/admin/stats_history.html.twig
Normal file
62
templates/admin/stats_history.html.twig
Normal file
|
@ -0,0 +1,62 @@
|
|||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<h2>Évolution du taux de complétion</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="completionHistoryChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const ctx = document.getElementById('completionHistoryChart').getContext('2d');
|
||||
|
||||
new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [
|
||||
{% for stat in statsHistory %}
|
||||
'{{ stat.date|date('d/m/Y') }}'{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
],
|
||||
datasets: [{
|
||||
label: 'Taux de complétion (%)',
|
||||
data: [
|
||||
{% for stat in statsHistory %}
|
||||
{{ stat.completionPercent }}{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
],
|
||||
borderColor: 'rgb(75, 192, 192)',
|
||||
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
||||
tension: 0.3,
|
||||
fill: true
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Évolution du taux de complétion au fil du temps'
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
max: 100,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Complétion (%)'
|
||||
}
|
||||
},
|
||||
x: {
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Date'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
|
@ -53,9 +53,8 @@
|
|||
{% block javascripts %}
|
||||
{{ parent() }}
|
||||
<script src='{{ asset('js/maplibre/maplibre-gl.js') }}'></script>
|
||||
<script type="module">
|
||||
import { colorizePercentageCells, setupCitySearch, getLabourerUrl, handleAddCityFormSubmit, colorizePercentageCellsRelative } from '{{ asset('js/utils.js') }}';
|
||||
|
||||
<script src='{{ asset('js/utils.js') }}'></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialiser les tooltips Bootstrap
|
||||
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
||||
|
|
|
@ -113,7 +113,10 @@
|
|||
<span class="zone">{{ stat.zone }}</span>
|
||||
<span class="name">{{ stat.name }}</span>
|
||||
</div>
|
||||
<span class="badge bg-primary rounded-pill">{{ stat.placesCount }} lieux</span>
|
||||
<div class="d-flex">
|
||||
<span class="badge bg-primary rounded-pill">{{ stat.placesCount }} lieux</span>
|
||||
<span class="badge rounded-pill completion {% if stat.completionPercent > 80 %}bg-success{% else %}bg-info{% endif %}" >{{ stat.completionPercent }}%</span>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue