mirror of
https://forge.chapril.org/tykayn/osm-commerces
synced 2025-06-20 01:44:42 +02:00
ajout stat hist
This commit is contained in:
parent
918527e15e
commit
7fb0c9c8c2
19 changed files with 695 additions and 314 deletions
|
@ -10,8 +10,8 @@
|
|||
|
||||
<div class="example-wrapper">
|
||||
<h1>Labourage fait sur la zone "{{ stats.zone }} {{stats.name}}" ✅</h1>
|
||||
<a href="{{ path('app_admin_labourer', {'zip_code': stats.zone}) }}" class="btn btn-primary" id="labourer">Labourer les mises à jour</a>
|
||||
<a href="{{ path('app_admin_stats', {'zip_code': stats.zone}) }}" class="btn btn-primary" id="labourer">Voir les résultats</a>
|
||||
<a href="{{ path('app_admin_labourer', {'insee_code': stats.zone}) }}" class="btn btn-primary" id="labourer">Labourer les mises à jour</a>
|
||||
<a href="{{ path('app_admin_stats', {'insee_code': stats.zone}) }}" class="btn btn-primary" id="labourer">Voir les résultats</a>
|
||||
|
||||
<p>
|
||||
lieux trouvés en plus: {{ new_places_counter }}
|
||||
|
|
|
@ -22,6 +22,13 @@
|
|||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
{{ parent() }}
|
||||
<script src="{{ asset('js/maplibre/maplibre-gl.js') }}"></script>
|
||||
<script src="{{ asset('js/turf/turf.min.js') }}"></script>
|
||||
<script src="{{ asset('js/map-utils.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container">
|
||||
<div class="mt-4 p-4">
|
||||
|
@ -31,7 +38,7 @@
|
|||
{{ stats.name }} - {{ stats.completionPercent }}% complété</h1>
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<a href="{{ path('app_admin_labourer', {'zip_code': stats.zone}) }}" class="btn btn-primary" id="labourer">Labourer les mises à jour</a>
|
||||
<a href="{{ path('app_admin_labourer', {'insee_code': stats.zone}) }}" class="btn btn-primary" id="labourer">Labourer les mises à jour</a>
|
||||
<button id="openInJOSM" class="btn btn-secondary ms-2">
|
||||
<i class="bi bi-map"></i> Ouvrir dans JOSM
|
||||
</button>
|
||||
|
@ -103,28 +110,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="completion-info mt-4">
|
||||
<div class="alert alert-info">
|
||||
<div class="d-flex align-items-center" style="cursor: pointer;" onclick="toggleCompletionInfo()">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
<h4 class="alert-heading mb-0">Comment est calculé le score de complétion ?</h4>
|
||||
<i class="bi bi-chevron-down ms-auto" id="completionInfoIcon"></i>
|
||||
</div>
|
||||
<div id="completionInfoContent" style="display: none;" class="mt-3">
|
||||
<p>Le score de complétion est calculé en fonction de plusieurs critères :</p>
|
||||
<ul>
|
||||
<li>Nom du commerce (obligatoire)</li>
|
||||
<li>Adresse complète (numéro, rue, code postal)</li>
|
||||
<li>Horaires d'ouverture</li>
|
||||
<li>Site web</li>
|
||||
<li>Numéro de téléphone</li>
|
||||
<li>Accessibilité PMR</li>
|
||||
<li>Note descriptive</li>
|
||||
</ul>
|
||||
<p>Chaque critère rempli augmente le score de complétion. Un commerce parfaitement renseigné aura un score de 100%.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="maploader">
|
||||
<div class="spinner-border" role="status">
|
||||
|
@ -156,7 +142,7 @@
|
|||
<h1 class="card-title p-4">Tableau des {{ stats.places |length }} lieux</h1>
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<a class="btn btn-primary pull-right mt-4" href="{{ path('app_admin_export_csv', {'zip_code': stats.zone}) }}" class="btn btn-primary">
|
||||
<a class="btn btn-primary pull-right mt-4" href="{{ path('app_admin_export_csv', {'insee_code': stats.zone}) }}" class="btn btn-primary">
|
||||
<i class="bi bi-filetype-csv"></i>
|
||||
Exporter en CSV
|
||||
</a>
|
||||
|
@ -185,15 +171,54 @@
|
|||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="history">
|
||||
<h2>Historique des {{ statsHistory|length }} stats</h2>
|
||||
<table class="table table-bordered table-striped table-hover table-responsive js-sort-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Places</th>
|
||||
<th>Complétion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for stat in statsHistory %}
|
||||
<tr>
|
||||
<td>{{ stat.date|date('d/m/Y') }}</td>
|
||||
<td>{{ stat.placesCount }}</td>
|
||||
<td>{{ stat.completionPercent }}%</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="completion-info mt-4">
|
||||
<div class="alert alert-info">
|
||||
<div class="d-flex align-items-center" style="cursor: pointer;" onclick="toggleCompletionInfo()">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
<p class="mb-0">Comment est calculé le score de complétion ?</p>
|
||||
<i class="bi bi-chevron-down ms-auto" id="completionInfoIcon"></i>
|
||||
</div>
|
||||
<div id="completionInfoContent" style="display: none;" class="mt-3">
|
||||
<p>Le score de complétion est calculé en fonction de plusieurs critères :</p>
|
||||
<ul>
|
||||
<li>Nom du commerce (obligatoire)</li>
|
||||
<li>Adresse complète (numéro, rue, code postal)</li>
|
||||
<li>Horaires d'ouverture</li>
|
||||
<li>Site web</li>
|
||||
<li>Numéro de téléphone</li>
|
||||
<li>Accessibilité PMR</li>
|
||||
<li>Note descriptive</li>
|
||||
</ul>
|
||||
<p>Chaque critère rempli augmente le score de complétion. Un commerce parfaitement renseigné aura un score de 100%.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Bouton caché pour JOSM -->
|
||||
<a id="josmButton" style="display: none;"></a>
|
||||
|
||||
<script src='{{ asset('js/maplibre/maplibre-gl.js') }}'></script>
|
||||
<script src='{{ asset('js/turf/turf.min.js') }}'></script>
|
||||
<script src="{{ asset('js/chartjs/chart.umd.js') }}"></script>
|
||||
<script src="{{ asset('js/app.js') }}"></script>
|
||||
<script>
|
||||
const request = `{{query_places|raw}}`;
|
||||
const zip_code = `{{stats.zone}}`;
|
||||
|
@ -207,99 +232,6 @@
|
|||
let dropMarkers = []; // Tableau pour stocker les marqueurs en goutte
|
||||
let contextMenu = null; // Menu contextuel
|
||||
|
||||
function getCompletionColor(completion) {
|
||||
if (completion === undefined || completion === null) {
|
||||
return '#808080'; // Gris pour pas d'information
|
||||
}
|
||||
// Convertir le pourcentage en couleur verte (0% = blanc, 100% = vert foncé)
|
||||
const intensity = Math.floor((completion / 100) * 255);
|
||||
return `rgb(0, ${intensity}, 0)`;
|
||||
}
|
||||
|
||||
function calculateCompletion(element) {
|
||||
let completionCount = 0;
|
||||
let totalFields = 0;
|
||||
let missingFields = [];
|
||||
|
||||
const fieldsToCheck = [
|
||||
{name: 'name', label: 'Nom du commerce'},
|
||||
{name: 'contact:street', label: 'Rue'},
|
||||
{name: 'contact:housenumber', label: 'Numéro'},
|
||||
{name: 'opening_hours', label: 'Horaires d\'ouverture'},
|
||||
{name: 'contact:website', label: 'Site web'},
|
||||
{name: 'contact:phone', label: 'Téléphone'},
|
||||
{name: 'wheelchair', label: 'Accessibilité PMR'}
|
||||
];
|
||||
|
||||
fieldsToCheck.forEach(field => {
|
||||
totalFields++;
|
||||
if (element.tags && element.tags[field.name]) {
|
||||
completionCount++;
|
||||
} else {
|
||||
missingFields.push(field.label);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
percentage: (completionCount / totalFields) * 100,
|
||||
missingFields: missingFields
|
||||
};
|
||||
}
|
||||
|
||||
function showMissingFieldsPopup(element) {
|
||||
const completion = calculateCompletion(element);
|
||||
if (completion.percentage < 100) {
|
||||
const popup = new maplibregl.Popup()
|
||||
.setLngLat(element.geometry.coordinates)
|
||||
.setHTML(`
|
||||
<div class="p-2">
|
||||
<h5>Informations manquantes pour ${element.tags?.name || 'ce commerce'}</h5>
|
||||
<ul class="list-unstyled">
|
||||
${completion.missingFields.map(field => `<li><i class="bi bi-x-circle text-danger"></i> ${field}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
`);
|
||||
popup.addTo(map);
|
||||
}
|
||||
}
|
||||
|
||||
function createPopupContent(element) {
|
||||
const completion = calculateCompletion(element);
|
||||
let content = `
|
||||
<div class="mb-2">
|
||||
<a class="btn btn-primary" href="/admin/placeType/${element.type}/${element.id}">
|
||||
<i class="bi bi-pencil"></i> ${element.tags?.name || 'Sans nom'}
|
||||
</a>
|
||||
<a class="btn btn-secondary ms-2" href="https://openstreetmap.org/${element.type}/${element.id}">
|
||||
<i class="bi bi-map"></i> OSM
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
|
||||
if (completion.percentage < 100) {
|
||||
content += `
|
||||
<div class="alert alert-warning mt-2">
|
||||
<h6>Informations manquantes :</h6>
|
||||
<ul class="list-unstyled mb-0">
|
||||
${completion.missingFields.map(field => `<li><i class="bi bi-x-circle text-danger"></i> ${field}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
content += '<table class="table table-sm">';
|
||||
|
||||
// Ajouter tous les tags
|
||||
if (element.tags) {
|
||||
for (const tag in element.tags) {
|
||||
content += `<tr><td><strong>${tag}</strong></td><td>${element.tags[tag]}</td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
content += '</table>';
|
||||
return content;
|
||||
}
|
||||
|
||||
function calculateCompletionDistribution(elements) {
|
||||
// Créer des buckets de 10% (0-10%, 10-20%, etc.)
|
||||
const buckets = Array(11).fill(0);
|
||||
|
@ -426,7 +358,7 @@
|
|||
});
|
||||
|
||||
// Afficher les marqueurs selon le type actuel
|
||||
updateMarkers();
|
||||
dropMarkers = updateMarkers(features, map, currentMarkerType, dropMarkers, overpassData);
|
||||
|
||||
// Ajuster la vue pour inclure tous les points
|
||||
const points = features.map(f => f.properties.center);
|
||||
|
@ -454,109 +386,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
function updateMarkers() {
|
||||
// Supprimer tous les marqueurs existants
|
||||
dropMarkers.forEach(marker => marker.remove());
|
||||
dropMarkers = [];
|
||||
|
||||
features.forEach(feature => {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'marker';
|
||||
el.style.backgroundColor = getCompletionColor(feature.properties.completion);
|
||||
el.style.width = '15px';
|
||||
el.style.height = '15px';
|
||||
el.style.borderRadius = '50%';
|
||||
el.style.border = '2px solid white';
|
||||
el.style.cursor = 'pointer';
|
||||
|
||||
const marker = new maplibregl.Marker(el)
|
||||
.setLngLat(feature.geometry.coordinates)
|
||||
.addTo(map);
|
||||
|
||||
// Ajouter l'événement de clic
|
||||
el.addEventListener('click', () => {
|
||||
const element = overpassData[feature.properties.id];
|
||||
if (element) {
|
||||
showMissingFieldsPopup(element);
|
||||
}
|
||||
});
|
||||
|
||||
dropMarkers.push(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function draw_circle_containing_all_features(map) {
|
||||
if (features.length === 0) return;
|
||||
|
||||
// Calculer le centre à partir de toutes les features
|
||||
const center = features.reduce((acc, feature) => {
|
||||
return [
|
||||
acc[0] + feature.properties.center[0],
|
||||
acc[1] + feature.properties.center[1]
|
||||
];
|
||||
}, [0, 0]).map(coord => coord / features.length);
|
||||
|
||||
// Calculer le rayon nécessaire pour couvrir tous les points
|
||||
const radius = features.reduce((maxRadius, feature) => {
|
||||
const distance = turf.distance(
|
||||
center,
|
||||
feature.properties.center,
|
||||
{ units: 'kilometers' }
|
||||
);
|
||||
return Math.max(maxRadius, distance);
|
||||
}, 0);
|
||||
|
||||
const circle = turf.circle(center, radius, { steps: 64, units: 'kilometers' });
|
||||
map.addSource('circle', { type: 'geojson', data: circle });
|
||||
map.addLayer({
|
||||
id: 'circle',
|
||||
type: 'fill',
|
||||
source: 'circle',
|
||||
paint: {
|
||||
'fill-color': 'blue',
|
||||
'fill-opacity': 0.25
|
||||
}
|
||||
});
|
||||
|
||||
// Ajouter un marqueur au centre du cercle
|
||||
new maplibregl.Marker()
|
||||
.setLngLat(center)
|
||||
.addTo(map);
|
||||
}
|
||||
|
||||
function updateCircles(map) {
|
||||
if (!map) return;
|
||||
|
||||
const zoom = map.getZoom();
|
||||
// Ajuster la taille de base en fonction du zoom
|
||||
// Plus le zoom est faible (loin), plus le rayon est grand
|
||||
const baseRadius = Math.min(100, 200 / Math.pow(1.2, zoom));
|
||||
|
||||
features.forEach(feature => {
|
||||
const source = map.getSource(feature.id);
|
||||
if (!source) return;
|
||||
|
||||
const completion = feature.properties.completion;
|
||||
const radius = baseRadius * (1 + (completion / 100));
|
||||
|
||||
// Mettre à jour le rayon du cercle
|
||||
const circle = turf.circle(
|
||||
feature.properties.center,
|
||||
0.5* radius / 1000, // Convertir en kilomètres
|
||||
{ steps: 64, units: 'kilometers' }
|
||||
);
|
||||
|
||||
// Mettre à jour la source avec le nouveau cercle
|
||||
source.setData(circle);
|
||||
|
||||
// Mettre à jour la couleur du cercle
|
||||
const layer = map.getLayer(feature.id);
|
||||
if (layer) {
|
||||
map.setPaintProperty(feature.id, 'fill-color', getCompletionColor(completion));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function openJOSMQuery(map, query) {
|
||||
const bounds = map.getBounds();
|
||||
|
||||
|
@ -639,58 +468,20 @@
|
|||
contextMenu.style.top = `${y}px`;
|
||||
|
||||
contextMenu.innerHTML = '<div class="spinner-border spinner-border-sm" role="status"></div> Recherche du code INSEE...';
|
||||
|
||||
try {
|
||||
// Récupérer les coordonnées du clic
|
||||
const { lng, lat } = e.lngLat;
|
||||
|
||||
// Appeler l'API pour obtenir le code INSEE
|
||||
const response = await fetch(`https://api-adresse.data.gouv.fr/reverse/?lon=${lng}&lat=${lat}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.features && data.features.length > 0) {
|
||||
const feature = data.features[0];
|
||||
const city = feature.properties.city;
|
||||
const inseeCode = feature.properties.citycode;
|
||||
|
||||
contextMenu.innerHTML = `
|
||||
<div class="mb-2">
|
||||
<strong>Ville :</strong> ${city}<br>
|
||||
<strong>Code INSEE :</strong> ${inseeCode}
|
||||
</div>
|
||||
<a href="/admin/labourer/${inseeCode}" class="btn btn-primary btn-sm">
|
||||
<i class="bi bi-tractor"></i> Labourer cette zone
|
||||
</a>
|
||||
`;
|
||||
} else {
|
||||
contextMenu.innerHTML = 'Aucune information trouvée pour cette position';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la recherche du code INSEE:', error);
|
||||
contextMenu.innerHTML = 'Erreur lors de la recherche du code INSEE';
|
||||
}
|
||||
});
|
||||
|
||||
// Fermer le menu contextuel lors d'un clic ailleurs
|
||||
document.addEventListener('click', function(e) {
|
||||
if (contextMenu && !contextMenu.contains(e.target)) {
|
||||
contextMenu.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Gestionnaires d'événements pour les boutons de type de marqueur
|
||||
document.getElementById('circleMarkersBtn').addEventListener('click', function() {
|
||||
currentMarkerType = 'circle';
|
||||
this.classList.add('active');
|
||||
document.getElementById('dropMarkersBtn').classList.remove('active');
|
||||
updateMarkers();
|
||||
dropMarkers = updateMarkers(features, map, currentMarkerType, dropMarkers, overpassData);
|
||||
});
|
||||
|
||||
document.getElementById('dropMarkersBtn').addEventListener('click', function() {
|
||||
currentMarkerType = 'drop';
|
||||
this.classList.add('active');
|
||||
document.getElementById('circleMarkersBtn').classList.remove('active');
|
||||
updateMarkers();
|
||||
dropMarkers = updateMarkers(features, map, currentMarkerType, dropMarkers, overpassData);
|
||||
});
|
||||
|
||||
document.getElementById('openInJOSM').addEventListener('click', openInJOSM);
|
||||
|
@ -720,8 +511,6 @@
|
|||
|
||||
// Charger les lieux
|
||||
loadPlaces(map);
|
||||
|
||||
draw_circle_containing_all_features(map);
|
||||
});
|
||||
|
||||
sortTable();
|
||||
|
|
|
@ -86,9 +86,9 @@
|
|||
<td class="{{ commerce.noteContent ? 'filled' : '' }}">{{ commerce.noteContent }}</td>
|
||||
<td class="{{ commerce.siret ? 'filled' : '' }}"> <a href="https://annuaire-entreprises.data.gouv.fr/etablissement/{{ commerce.siret }}" > {{ commerce.siret }}</a></td>
|
||||
<td>
|
||||
<a href="https://www.openstreetmap.org/{{ commerce.osmKind }}/{{ commerce.osmId }}" >
|
||||
<a href="https://www.openstreetmap.org/{{ commerce.osmKind }}/{{ commerce.osmId }}" title="{{ commerce.osmKind }} - {{ commerce.osmId }} " >
|
||||
<i class="bi bi-globe"></i>
|
||||
{{ commerce.osmId }}
|
||||
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
|
|
|
@ -21,8 +21,10 @@
|
|||
<i class="bi bi-house-fill"></i>
|
||||
Rue
|
||||
</th>
|
||||
<th>
|
||||
<i class="bi bi-globe"></i>
|
||||
Site web ({{ stats.getAvecSite() }} / {{ stats.places|length }})</th>
|
||||
Site web ({{ stats.getAvecSite() }} / {{ stats.places|length }})
|
||||
</th>
|
||||
<th>
|
||||
<i class="bi bi-wheelchair"></i>
|
||||
<i class="bi bi-person-fill-slash"></i>
|
||||
|
|
|
@ -129,25 +129,29 @@
|
|||
<tbody>
|
||||
{% for stat in stats %}
|
||||
<tr>
|
||||
<td><a href="{{ path('app_admin_stats', {'zip_code': stat.zone}) }}" title="Voir les statistiques de cette ville">
|
||||
<td><a href="{{ path('app_admin_stats', {'insee_code': stat.zone}) }}" title="Voir les statistiques de cette ville">
|
||||
{{ stat.name }}
|
||||
{% if not stat.name and stat.zone starts with '751' %}
|
||||
Paris {{ stat.zone|slice(-2) }}e.
|
||||
|
||||
{% endif %}
|
||||
</a></td>
|
||||
<td>{{ stat.zone }}</td>
|
||||
<td>{{ stat.completionPercent }}%</td>
|
||||
<td>{{ stat.places|length }}</td>
|
||||
<td>
|
||||
<div class="btn-group" role="group">
|
||||
<a href="{{ path('app_admin_stats', {'zip_code': stat.zone}) }}" class="btn btn-sm btn-primary" title="Voir les statistiques de cette ville">
|
||||
<a href="{{ path('app_admin_stats', {'insee_code': stat.zone}) }}" class="btn btn-sm btn-primary" title="Voir les statistiques de cette ville">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
<a href="{{ path('app_admin_labourer', {'zip_code': stat.zone}) }}"
|
||||
<a href="{{ path('app_admin_labourer', {'insee_code': stat.zone}) }}"
|
||||
class="btn btn-sm btn-success btn-labourer"
|
||||
data-zip-code="{{ stat.zone }}"
|
||||
title="Labourer cette ville"
|
||||
>
|
||||
<i class="bi bi-recycle"></i>
|
||||
</a>
|
||||
<a href="{{ path('app_admin_delete_by_zone', {'zip_code': stat.zone}) }}"
|
||||
<a href="{{ path('app_admin_delete_by_zone', {'insee_code': stat.zone}) }}"
|
||||
class="btn btn-sm btn-danger"
|
||||
onclick="return confirm('Êtes-vous sûr de vouloir supprimer cette zone ?')"
|
||||
title="Supprimer cette ville"
|
||||
|
|
|
@ -126,22 +126,24 @@
|
|||
<div class="lien-OpenStreetMap">
|
||||
<a href="https://www.openstreetmap.org/{{commerce.osmKind}}/{{ commerce_overpass['@attributes'].id }}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-globe"></i> {{ 'display.view_on_osm'|trans }}
|
||||
{{ asset('img/logo-osm.png') }}
|
||||
<img src="{{ asset('img/logo-osm.png') }}" alt="OpenStreetMap" class="img-fluid thumbnail">
|
||||
</a>
|
||||
<button onclick="openInJOSM('{{commerce.osmKind}}', '{{ commerce_overpass['@attributes'].id }}')"
|
||||
title="Ouvrir dans JOSM"
|
||||
class="btn btn-outline-secondary ms-2">
|
||||
<i class="bi bi-pencil"></i>
|
||||
{{ asset('img/josm.png') }}
|
||||
<img src="{{ asset('img/josm.png') }}" alt="JOSM" class="img-fluid thumbnail">
|
||||
</button>
|
||||
<button onclick="openInPanoramax()" title="Ouvrir dans Panoramax" class="btn btn-outline-secondary ms-2">
|
||||
<i class="bi bi-camera"></i>
|
||||
{{ asset('img/logo-panoramax.png') }}
|
||||
|
||||
<img src="{{ asset('img/panoramax.svg') }}" alt="Panoramax" class="img-fluid thumbnail">
|
||||
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{% if commerce.stats %}
|
||||
<a href="{{ path('app_admin_stats', {'zip_code': commerce.stats.zone}) }}" class="btn btn-outline-secondary">
|
||||
<a href="{{ path('app_admin_stats', {'insee_code': commerce.stats.zone}) }}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-bar-chart"></i> zone {{commerce.stats.zone}}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
|
|
@ -108,7 +108,7 @@
|
|||
</div>
|
||||
|
||||
{% for stat in stats %}
|
||||
<a href="{{ path('app_admin_stats', {'zip_code': stat.zone}) }}" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
|
||||
<a href="{{ path('app_admin_stats', {'insee_code': stat.zone}) }}" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
|
||||
<div class="d-flex flex-column">
|
||||
<span class="zone">{{ stat.zone }}</span>
|
||||
<span class="name">{{ stat.name }}</span>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue