mirror of
https://forge.chapril.org/tykayn/osm-commerces
synced 2025-10-04 17:04:53 +02:00
tests liens ctc
This commit is contained in:
parent
6f3d19245e
commit
2fd0d8d933
3 changed files with 315 additions and 78 deletions
|
@ -468,10 +468,11 @@ final class AdminController extends AbstractController
|
||||||
'followup_labels' => \App\Service\FollowUpService::getFollowUpThemes(),
|
'followup_labels' => \App\Service\FollowUpService::getFollowUpThemes(),
|
||||||
'followup_icons' => \App\Service\FollowUpService::getFollowUpIcons(),
|
'followup_icons' => \App\Service\FollowUpService::getFollowUpIcons(),
|
||||||
'progression7Days' => $progression7Days,
|
'progression7Days' => $progression7Days,
|
||||||
|
'all_types' => \App\Service\FollowUpService::getFollowUpThemes(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/admin/stats/{insee_code}/followup-graph/{theme}', name: 'admin_followup_theme_graph', requirements: ['insee_code' => '\\d+'])]
|
#[Route('/admin/stats/{insee_code}/followup-graph/{theme}', name: 'admin_followup_theme_graph', requirements: ['insee_code' => '\d+'])]
|
||||||
public function followupThemeGraph(string $insee_code, string $theme): Response
|
public function followupThemeGraph(string $insee_code, string $theme): Response
|
||||||
{
|
{
|
||||||
$stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
|
$stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
|
||||||
|
@ -510,11 +511,94 @@ final class AdminController extends AbstractController
|
||||||
usort($countData, fn($a, $b) => $a['date'] <=> $b['date']);
|
usort($countData, fn($a, $b) => $a['date'] <=> $b['date']);
|
||||||
usort($completionData, fn($a, $b) => $a['date'] <=> $b['date']);
|
usort($completionData, fn($a, $b) => $a['date'] <=> $b['date']);
|
||||||
|
|
||||||
$this->actionLogger->log('followup_theme_graph', [
|
// Récupérer les objets du thème (Place) pour la ville
|
||||||
'insee_code' => $insee_code,
|
$places = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $insee_code]);
|
||||||
'theme' => $theme,
|
$motocultrice = $this->motocultrice;
|
||||||
'theme_label' => $themes[$theme] ?? 'Unknown'
|
$objects = [];
|
||||||
]);
|
// Récupérer la correspondance thème <-> requête Overpass
|
||||||
|
$themeQueries = \App\Service\FollowUpService::getFollowUpOverpassQueries();
|
||||||
|
$overpass_type_query = $themeQueries[$theme] ?? '';
|
||||||
|
if ($overpass_type_query) {
|
||||||
|
$overpass_query = "[out:json][timeout:60];\narea[\"ref:INSEE\"=\"$insee_code\"]->.searchArea;\n($overpass_type_query);\n(._;>;);\nout meta;\n>;";
|
||||||
|
$josm_url = 'http://127.0.0.1:8111/import?url=https://overpass-api.de/api/interpreter?data=' . urlencode($overpass_query);
|
||||||
|
} else {
|
||||||
|
$josm_url = null;
|
||||||
|
}
|
||||||
|
// Fonction utilitaire pour extraire clé/valeur de la requête Overpass
|
||||||
|
$extractTag = function($query) {
|
||||||
|
if (preg_match('/\\[([a-zA-Z0-9:_-]+)\\]="([^"]+)"/', $query, $matches)) {
|
||||||
|
return [$matches[1], $matches[2]];
|
||||||
|
}
|
||||||
|
return [null, null];
|
||||||
|
};
|
||||||
|
list($tagKey, $tagValue) = $extractTag($themeQueries[$theme] ?? '');
|
||||||
|
foreach ($places as $place) {
|
||||||
|
$match = false;
|
||||||
|
// Cas particulier healthcare
|
||||||
|
if ($theme === 'healthcare') {
|
||||||
|
$main_tag = $place->getMainTag();
|
||||||
|
if ($main_tag && (
|
||||||
|
str_starts_with($main_tag, 'healthcare=') ||
|
||||||
|
in_array($main_tag, [
|
||||||
|
'amenity=doctors',
|
||||||
|
'amenity=pharmacy',
|
||||||
|
'amenity=hospital',
|
||||||
|
'amenity=clinic',
|
||||||
|
'amenity=social_facility'
|
||||||
|
])
|
||||||
|
)) {
|
||||||
|
$match = true;
|
||||||
|
}
|
||||||
|
} elseif ($tagKey && $tagValue) {
|
||||||
|
// On tente de retrouver la valeur du tag dans les propriétés Place
|
||||||
|
$main_tag = $place->getMainTag();
|
||||||
|
if ($main_tag === "$tagKey=$tagValue") {
|
||||||
|
$match = true;
|
||||||
|
}
|
||||||
|
// Cas particuliers pour certains tags stockés ailleurs
|
||||||
|
if (!$match) {
|
||||||
|
if ($tagKey === 'highway' && method_exists($place, 'getOsmKind') && $place->getOsmKind() === $tagValue) {
|
||||||
|
$match = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($match && $place->getLat() && $place->getLon()) {
|
||||||
|
$objects[] = [
|
||||||
|
'id' => $place->getOsmId(),
|
||||||
|
'osm_kind' => $place->getOsmKind(),
|
||||||
|
'lat' => $place->getLat(),
|
||||||
|
'lon' => $place->getLon(),
|
||||||
|
'name' => $place->getName(),
|
||||||
|
'tags' => [ 'main_tag' => $place->getMainTag() ],
|
||||||
|
'is_complete' => !empty($place->getName()),
|
||||||
|
'osm_url' => 'https://www.openstreetmap.org/' . $place->getOsmKind() . '/' . $place->getOsmId(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$geojson = [
|
||||||
|
'type' => 'FeatureCollection',
|
||||||
|
'features' => array_map(function($obj) {
|
||||||
|
return [
|
||||||
|
'type' => 'Feature',
|
||||||
|
'geometry' => [
|
||||||
|
'type' => 'Point',
|
||||||
|
'coordinates' => [$obj['lon'], $obj['lat']]
|
||||||
|
],
|
||||||
|
'properties' => $obj
|
||||||
|
];
|
||||||
|
}, $objects)
|
||||||
|
];
|
||||||
|
|
||||||
|
// Centre de la carte : centre géographique des objets ou de la ville
|
||||||
|
$center = null;
|
||||||
|
if (count($objects) > 0) {
|
||||||
|
$lat = array_sum(array_column($objects, 'lat')) / count($objects);
|
||||||
|
$lon = array_sum(array_column($objects, 'lon')) / count($objects);
|
||||||
|
$center = [$lon, $lat];
|
||||||
|
} elseif ($stats->getPlaces()->count() > 0) {
|
||||||
|
$first = $stats->getPlaces()->first();
|
||||||
|
$center = [$first->getLon(), $first->getLat()];
|
||||||
|
}
|
||||||
|
|
||||||
return $this->render('admin/followup_theme_graph.html.twig', [
|
return $this->render('admin/followup_theme_graph.html.twig', [
|
||||||
'stats' => $stats,
|
'stats' => $stats,
|
||||||
|
@ -523,6 +607,10 @@ final class AdminController extends AbstractController
|
||||||
'count_data' => json_encode($countData),
|
'count_data' => json_encode($countData),
|
||||||
'completion_data' => json_encode($completionData),
|
'completion_data' => json_encode($completionData),
|
||||||
'icons' => \App\Service\FollowUpService::getFollowUpIcons(),
|
'icons' => \App\Service\FollowUpService::getFollowUpIcons(),
|
||||||
|
'geojson' => json_encode($geojson),
|
||||||
|
'josm_url' => $josm_url,
|
||||||
|
'center' => $center,
|
||||||
|
'maptiler_token' => $_ENV['MAPTILER_TOKEN'] ?? null,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
{% block stylesheets %}
|
{% block stylesheets %}
|
||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
|
<link href='{{ asset('js/maplibre/maplibre-gl.css') }}' rel='stylesheet'/>
|
||||||
<style>
|
<style>
|
||||||
.chart-container {
|
.chart-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -87,11 +88,48 @@
|
||||||
.chart-content.active {
|
.chart-content.active {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#themeMap {
|
||||||
|
height: 400px;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
.maplibregl-popup-content {
|
||||||
|
font-size: 0.95em;
|
||||||
|
}
|
||||||
|
.btn-josm {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
|
{# DEBUG : Affichage des objets Place trouvés pour cette ville #}
|
||||||
|
{% if places is defined %}
|
||||||
|
<div class="alert alert-warning" style="font-size:0.95em;">
|
||||||
|
<b>DEBUG : Objets Place trouvés pour cette ville (avant filtrage)</b><br>
|
||||||
|
<table class="table table-sm table-bordered mt-2 mb-0">
|
||||||
|
<thead><tr>
|
||||||
|
<th>#</th><th>id</th><th>main_tag</th><th>osm_kind</th><th>nom</th><th>lat</th><th>lon</th>
|
||||||
|
</tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{% for p in places %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ loop.index }}</td>
|
||||||
|
<td>{{ p.getOsmId() }}</td>
|
||||||
|
<td>{{ p.getMainTag() }}</td>
|
||||||
|
<td>{{ p.getOsmKind() }}</td>
|
||||||
|
<td>{{ p.getName() }}</td>
|
||||||
|
<td>{{ p.getLat() }}</td>
|
||||||
|
<td>{{ p.getLon() }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="stats-header">
|
<div class="stats-header">
|
||||||
<div class="d-flex justify-content-between align-items-start">
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
<div>
|
<div>
|
||||||
|
@ -125,6 +163,15 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if josm_url %}
|
||||||
|
<a href="{{ josm_url }}" class="btn btn-outline-dark btn-josm" target="_blank">
|
||||||
|
<i class="bi bi-box-arrow-up-right"></i> Ouvrir tous les objets dans JOSM
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-info mb-3">Aucun objet sélectionné pour ce thème, rien à charger dans JOSM.</div>
|
||||||
|
{% endif %}
|
||||||
|
<div id="themeMap"></div>
|
||||||
|
|
||||||
<div class="stats-grid">
|
<div class="stats-grid">
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<div class="stat-value" id="currentCount">-</div>
|
<div class="stat-value" id="currentCount">-</div>
|
||||||
|
@ -165,6 +212,65 @@
|
||||||
|
|
||||||
{% block javascripts %}
|
{% block javascripts %}
|
||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
|
<script src='{{ asset('js/maplibre/maplibre-gl.js') }}'></script>
|
||||||
|
<script>
|
||||||
|
const geojson = {{ geojson|raw }};
|
||||||
|
const mapToken = '{{ maptiler_token }}';
|
||||||
|
const mapCenter = {{ center|json_encode|raw }};
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
console.log('[DEBUG] mapToken:', mapToken);
|
||||||
|
console.log('[DEBUG] geojson:', geojson);
|
||||||
|
console.log('[DEBUG] geojson.features:', geojson ? geojson.features : undefined);
|
||||||
|
console.log('[DEBUG] mapCenter:', mapCenter);
|
||||||
|
if (mapToken && geojson && geojson.features && geojson.features.length > 0) {
|
||||||
|
console.log('[DEBUG] Initialisation de la carte Maplibre...');
|
||||||
|
const map = new maplibregl.Map({
|
||||||
|
container: 'themeMap',
|
||||||
|
style: `https://api.maptiler.com/maps/streets/style.json?key=${mapToken}`,
|
||||||
|
center: mapCenter || geojson.features[0].geometry.coordinates,
|
||||||
|
zoom: 13
|
||||||
|
});
|
||||||
|
map.addControl(new maplibregl.NavigationControl());
|
||||||
|
geojson.features.forEach(f => {
|
||||||
|
let color = f.properties.is_complete ? '#198754' : '#adb5bd';
|
||||||
|
if (!f.properties.is_complete && (f.properties.tags && (f.properties.tags.name || f.properties.tags.operator))) {
|
||||||
|
color = '#ffc107'; // partiel
|
||||||
|
}
|
||||||
|
const marker = new maplibregl.Marker({ color: color })
|
||||||
|
.setLngLat(f.geometry.coordinates)
|
||||||
|
.setPopup(new maplibregl.Popup({ offset: 18 })
|
||||||
|
.setHTML(`
|
||||||
|
<div style='min-width:180px'>
|
||||||
|
<strong>${f.properties.name || '(sans nom)'}</strong><br>
|
||||||
|
<span class='text-muted'>${f.properties.osm_kind} ${f.properties.id}</span><br>
|
||||||
|
<span style='font-size:0.95em;'>
|
||||||
|
${Object.entries(f.properties.tags).map(([k,v]) => `<span><b>${k}</b>: ${v}</span>`).join('<br>')}
|
||||||
|
</span>
|
||||||
|
<a href='${f.properties.osm_url}' target='_blank'>Voir sur OSM</a>
|
||||||
|
</div>
|
||||||
|
`)
|
||||||
|
)
|
||||||
|
.addTo(map);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.warn('[DEBUG] Carte non initialisée : conditions non remplies.');
|
||||||
|
if (!mapToken) {
|
||||||
|
console.warn('[DEBUG] mapToken manquant ou vide');
|
||||||
|
}
|
||||||
|
if (!geojson) {
|
||||||
|
console.warn('[DEBUG] geojson manquant ou vide');
|
||||||
|
}
|
||||||
|
if (!geojson || !geojson.features || geojson.features.length === 0) {
|
||||||
|
console.warn('[DEBUG] geojson.features vide ou non défini');
|
||||||
|
}
|
||||||
|
// Affichage d'un message dans la page
|
||||||
|
const mapDiv = document.getElementById('themeMap');
|
||||||
|
if (mapDiv) {
|
||||||
|
mapDiv.innerHTML = '<div style="color:red;font-weight:bold;padding:2em;text-align:center;">Carte non initialisée : données manquantes ou incomplètes (voir console pour debug)</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
|
||||||
|
|
||||||
|
|
|
@ -218,14 +218,12 @@
|
||||||
</ul>
|
</ul>
|
||||||
<div class="tab-content" id="themeTabsContent">
|
<div class="tab-content" id="themeTabsContent">
|
||||||
<div class="tab-pane fade show active" id="tabTableContent" role="tabpanel" aria-labelledby="tab-table">
|
<div class="tab-pane fade show active" id="tabTableContent" role="tabpanel" aria-labelledby="tab-table">
|
||||||
<table class="table table-sm table-theme align-middle">
|
<table class="table table-theme">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Catégorie</th>
|
|
||||||
<th>Thème</th>
|
<th>Thème</th>
|
||||||
<th>Nombre</th>
|
<th>Nombre</th>
|
||||||
<th>Complétion</th>
|
<th>Complétion</th>
|
||||||
<th>Progression 7j</th>
|
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -294,79 +292,51 @@
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade" id="tabCardsContent" role="tabpanel" aria-labelledby="tab-cards">
|
<div class="tab-pane fade" id="tabCardsContent" role="tabpanel" aria-labelledby="tab-cards">
|
||||||
{% for group_name, group_types in theme_groups %}
|
{% set all_types = followup_labels|keys %}
|
||||||
<div class="mb-2">
|
<div class="row">
|
||||||
<div class="mb-1 text-muted">
|
{% for type in all_types %}
|
||||||
{% if group_name == 'emergency' %}🚨 Urgence
|
{% set data = latestFollowups[type]|default(null) %}
|
||||||
{% elseif group_name == 'transport' %}🚌 Transport
|
{% set overpass_query = '[out:json][timeout:60];\narea["ref:INSEE"="' ~ stats.zone ~ '"]->.searchArea;\n(' ~ overpass_type_queries[type]|default('') ~ ');\n(._;>;);\nout meta;\n>;' %}
|
||||||
{% elseif group_name == 'healthcare' %}🏥 Santé
|
{% set completion = data and data.completion is defined ? data.completion.getMeasure() : null %}
|
||||||
{% elseif group_name == 'education' %}🎓 Éducation
|
{% set completion_class = '' %}
|
||||||
{% elseif group_name == 'security' %}🛡️ Sécurité
|
{% if completion is not null %}
|
||||||
{% elseif group_name == 'infrastructure' %}🏗️ Infrastructure
|
{% if completion < 40 %}
|
||||||
{% else %}{{ group_name|capitalize }}
|
{% set completion_class = 'completion-low' %}
|
||||||
|
{% elseif completion < 80 %}
|
||||||
|
{% set completion_class = 'completion-medium' %}
|
||||||
|
{% else %}
|
||||||
|
{% set completion_class = 'completion-high' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
{% endif %}
|
||||||
<div class="theme-row-scroll">
|
<div class="col-auto">
|
||||||
{% for type in group_types %}
|
<div class="card shadow-sm text-center compact-theme-card" style="min-width: 120px; max-width: 140px;">
|
||||||
{% set data = latestFollowups[type]|default(null) %}
|
<div class="card-body p-2">
|
||||||
{% set overpass_query = '[out:json][timeout:60];\narea["ref:INSEE"="' ~ stats.zone ~ '"]->.searchArea;\n(' ~ overpass_type_queries[type]|default('') ~ ');\n(._;>;);\nout meta;\n>;' %}
|
<div class="d-flex align-items-center justify-content-between mb-1">
|
||||||
{% set completion = data and data.completion is defined ? data.completion.getMeasure() : null %}
|
<span class="completion-badge {{ completion_class }}"></span>
|
||||||
{% set completion_class = '' %}
|
<i class="bi {{ followup_icons[type]|default('bi-question-circle') }} fs-4"></i>
|
||||||
{% if completion is not null %}
|
</div>
|
||||||
{% if completion < 40 %}
|
<div class="theme-title mb-1">
|
||||||
{% set completion_class = 'completion-low' %}
|
<a href="https://overpass-api.de/api/interpreter?data={{ overpass_query|url_encode }}" target="_blank" class="fw-bold text-decoration-none text-dark small" title="Voir le JSON Overpass">
|
||||||
{% elseif completion < 80 %}
|
{{ followup_labels[type]|default(type|capitalize) }}
|
||||||
{% set completion_class = 'completion-medium' %}
|
</a>
|
||||||
{% else %}
|
</div>
|
||||||
{% set completion_class = 'completion-high' %}
|
<div class="theme-stats small">
|
||||||
{% endif %}
|
<span title="Nombre">{{ data and data.count is defined ? data.count.getMeasure() : '?' }}</span> |
|
||||||
{% endif %}
|
<span title="Complétion">{{ completion is not null ? completion : '?' }}%</span>
|
||||||
<div class="col-auto">
|
</div>
|
||||||
<div class="card shadow-sm text-center compact-theme-card" style="min-width: 120px; max-width: 140px;">
|
<div class="theme-actions mt-1">
|
||||||
<div class="card-body p-2">
|
<a href="{{ path('admin_followup_theme_graph', {'insee_code': stats.zone, 'theme': type}) }}" target="_blank" class="btn btn-sm btn-outline-primary btn-sm" title="Voir le graphique">
|
||||||
<div class="d-flex align-items-center justify-content-between mb-1">
|
<i class="bi bi-graph-up"></i>
|
||||||
<span class="completion-badge {{ completion_class }}"></span>
|
</a>
|
||||||
<i class="bi {{ followup_icons[type]|default('bi-question-circle') }} fs-4"></i>
|
<a href="http://127.0.0.1:8111/import?url=https://overpass-api.de/api/interpreter?data={{ overpass_query|url_encode }}" target="_blank" class="btn btn-sm btn-outline-dark btn-sm ms-1" title="Ouvrir dans JOSM">
|
||||||
</div>
|
<i class="bi bi-box-arrow-up-right"></i> JOSM
|
||||||
<div class="theme-title mb-1">
|
</a>
|
||||||
<a href="http://127.0.0.1:8111/import?url=https://overpass-api.de/api/interpreter?data={{ overpass_query|url_encode }}" target="_blank" class="fw-bold text-decoration-none text-dark small" title="Charger dans JOSM">
|
|
||||||
{{ followup_labels[type]|default(type|capitalize) }}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="theme-stats small">
|
|
||||||
<span title="Nombre">{{ data and data.count is defined ? data.count.getMeasure() : '?' }}</span> |
|
|
||||||
<span title="Complétion">{{ completion is not null ? completion : '?' }}%</span>
|
|
||||||
</div>
|
|
||||||
<div class="theme-actions mt-1">
|
|
||||||
<a href="{{ path('admin_followup_theme_graph', {'insee_code': stats.zone, 'theme': type}) }}" target="_blank" class="btn btn-sm btn-outline-primary btn-sm" title="Voir le graphique">
|
|
||||||
<i class="bi bi-graph-up"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% if progression7Days[type] is defined %}
|
|
||||||
{% set countDelta = progression7Days[type].count %}
|
|
||||||
{% set completionDelta = progression7Days[type].completion %}
|
|
||||||
{% if countDelta is not null or completionDelta is not null %}
|
|
||||||
<small class="text-muted d-block mt-1">
|
|
||||||
{% if countDelta is not null %}
|
|
||||||
<span title="Progression sur 7 jours - Nombre d'objets">
|
|
||||||
{{ countDelta > 0 ? '+' ~ countDelta : countDelta == 0 ? '0' : countDelta }}
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
{% if completionDelta is not null %}
|
|
||||||
<span title="Progression sur 7 jours - Complétion">
|
|
||||||
{{ completionDelta > 0 ? '+' ~ completionDelta|round(1) : completionDelta == 0 ? '0' : completionDelta|round(1) }}%
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
</small>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endfor %}
|
||||||
{% endfor %}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -603,6 +573,48 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Espace de dump JSON -->
|
||||||
|
<div id="ctc-json-dump-container" class="mt-4" style="display:none;">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header p-2">
|
||||||
|
<i class="bi bi-file-earmark-code"></i> Dump du JSON récupéré
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-2">
|
||||||
|
<pre id="ctc-json-dump" style="max-height:400px;overflow:auto;"></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3 ctc-tests">
|
||||||
|
<div class="card ctc-tests">
|
||||||
|
<div class="card-header p-2">
|
||||||
|
<i class="bi bi-link-45deg"></i> Tester les JSON Complète tes commerces
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-2">
|
||||||
|
<ul class="mb-0" style="font-size:0.95em;">
|
||||||
|
{% set ctc_jsons = stats.getAllCTCUrlsMap() %}
|
||||||
|
{% for key, url in ctc_jsons %}
|
||||||
|
<li><a href="{{ url }}" target="_blank" rel="noopener">{{ key }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
<div class="mt-3">
|
||||||
|
<div class="input-group">
|
||||||
|
<select id="ctc-json-select" class="form-select">
|
||||||
|
<option value="">Choisir un JSON à tester…</option>
|
||||||
|
{% for key, url in ctc_jsons %}
|
||||||
|
<option value="{{ url }}">{{ key }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<button id="ctc-json-test-btn" class="btn btn-outline-primary" type="button">
|
||||||
|
<i class="bi bi-bug"></i> Tester l'accès JSON CTC
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="ctc-json-error" class="alert alert-danger mt-4" style="display:none;"></div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -1048,4 +1060,35 @@ if(dc ){
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const btn = document.getElementById('ctc-json-test-btn');
|
||||||
|
const select = document.getElementById('ctc-json-select');
|
||||||
|
const dumpContainer = document.getElementById('ctc-json-dump-container');
|
||||||
|
const dump = document.getElementById('ctc-json-dump');
|
||||||
|
const error = document.getElementById('ctc-json-error');
|
||||||
|
if(btn && select) {
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
const url = select.value;
|
||||||
|
dumpContainer.style.display = 'none';
|
||||||
|
error.style.display = 'none';
|
||||||
|
dump.textContent = '';
|
||||||
|
if(!url) return;
|
||||||
|
fetch(url)
|
||||||
|
.then(r => {
|
||||||
|
if(!r.ok) throw new Error('Erreur HTTP ' + r.status);
|
||||||
|
return r.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
dump.textContent = JSON.stringify(data, null, 2);
|
||||||
|
dumpContainer.style.display = '';
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
error.textContent = 'Erreur lors de la récupération du JSON : ' + e.message;
|
||||||
|
error.style.display = '';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue