From c7e4f4e6a29e63642531d317920ad9eee905e4ed Mon Sep 17 00:00:00 2001 From: Tykayn Date: Sat, 5 Jul 2025 17:21:18 +0200 Subject: [PATCH] =?UTF-8?q?centralisation=20des=20tags=20de=20compl=C3=A9t?= =?UTF-8?q?ion=20par=20th=C3=A8me?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Controller/AdminController.php | 293 ++++++++++++++++++ src/Controller/FollowUpController.php | 1 + src/Service/FollowUpService.php | 250 ++++----------- .../admin/_followup_completion_tags.html.twig | 29 ++ templates/admin/followup_graph.html.twig | 5 + .../admin/followup_theme_graph.html.twig | 17 + templates/admin/stats.html.twig | 22 ++ 7 files changed, 433 insertions(+), 184 deletions(-) create mode 100644 templates/admin/_followup_completion_tags.html.twig diff --git a/src/Controller/AdminController.php b/src/Controller/AdminController.php index b9bf9907..85209860 100644 --- a/src/Controller/AdminController.php +++ b/src/Controller/AdminController.php @@ -476,6 +476,7 @@ final class AdminController extends AbstractController 'progression7Days' => $progression7Days, 'all_types' => \App\Service\FollowUpService::getFollowUpThemes(), 'getTagEmoji' => [self::class, 'getTagEmoji'], + 'completion_tags' => \App\Service\FollowUpService::getFollowUpCompletionTags(), ]); } @@ -621,6 +622,7 @@ final class AdminController extends AbstractController 'josm_url' => $josm_url, 'center' => $center, 'maptiler_token' => $_ENV['MAPTILER_TOKEN'] ?? null, + 'completion_tags' => \App\Service\FollowUpService::getFollowUpCompletionTags(), ]); } @@ -1922,4 +1924,295 @@ final class AdminController extends AbstractController default => '🏷️', }; } + + public function followupEmbedGraph(Request $request, string $insee_code, string $theme): Response + { + $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]); + if (!$stats) { + $this->addFlash('error', 'Aucune stats trouvée pour ce code INSEE.'); + return $this->redirectToRoute('app_admin'); + } + + $themes = \App\Service\FollowUpService::getFollowUpThemes(); + if (!isset($themes[$theme])) { + $this->addFlash('error', 'Thème non reconnu.'); + return $this->redirectToRoute('app_admin_stats', ['insee_code' => $insee_code]); + } + + // Récupérer toutes les données de followup pour ce thème + $followups = $stats->getCityFollowUps(); + $countData = []; + $completionData = []; + + foreach ($followups as $fu) { + if ($fu->getName() === $theme . '_count') { + $countData[] = [ + 'date' => $fu->getDate()->format('Y-m-d'), + 'value' => $fu->getMeasure() + ]; + } + if ($fu->getName() === $theme . '_completion') { + $completionData[] = [ + 'date' => $fu->getDate()->format('Y-m-d'), + 'value' => $fu->getMeasure() + ]; + } + } + + // Trier par date + usort($countData, fn($a, $b) => $a['date'] <=> $b['date']); + usort($completionData, fn($a, $b) => $a['date'] <=> $b['date']); + + // Récupérer les objets du thème (Place) pour la ville + $places = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $insee_code]); + $motocultrice = $this->motocultrice; + $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(), + 'uuid' => $place->getUuidForUrl(), + 'zip_code' => $place->getZipCode(), + ]; + } + } + $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_embed_graph.html.twig', [ + 'stats' => $stats, + 'theme' => $theme, + 'theme_label' => $themes[$theme], + 'count_data' => json_encode($countData), + 'completion_data' => json_encode($completionData), + 'icons' => \App\Service\FollowUpService::getFollowUpIcons(), + 'geojson' => json_encode($geojson), + 'overpass_query' => $overpass_query, + 'josm_url' => $josm_url, + 'center' => $center, + 'maptiler_token' => $_ENV['MAPTILER_TOKEN'] ?? null, + 'completion_tags' => \App\Service\FollowUpService::getFollowUpCompletionTags(), + ]); + } + + public function followupGraph(Request $request, string $insee_code): Response + { + $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]); + if (!$stats) { + $this->addFlash('error', 'Aucune stats trouvée pour ce code INSEE.'); + return $this->redirectToRoute('app_admin'); + } + + $themes = \App\Service\FollowUpService::getFollowUpThemes(); + if (!isset($themes[$theme])) { + $this->addFlash('error', 'Thème non reconnu.'); + return $this->redirectToRoute('app_admin_stats', ['insee_code' => $insee_code]); + } + + // Récupérer toutes les données de followup pour ce thème + $followups = $stats->getCityFollowUps(); + $countData = []; + $completionData = []; + + foreach ($followups as $fu) { + if ($fu->getName() === $theme . '_count') { + $countData[] = [ + 'date' => $fu->getDate()->format('Y-m-d'), + 'value' => $fu->getMeasure() + ]; + } + if ($fu->getName() === $theme . '_completion') { + $completionData[] = [ + 'date' => $fu->getDate()->format('Y-m-d'), + 'value' => $fu->getMeasure() + ]; + } + } + + // Trier par date + usort($countData, fn($a, $b) => $a['date'] <=> $b['date']); + usort($completionData, fn($a, $b) => $a['date'] <=> $b['date']); + + // Récupérer les objets du thème (Place) pour la ville + $places = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $insee_code]); + $motocultrice = $this->motocultrice; + $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(), + 'uuid' => $place->getUuidForUrl(), + 'zip_code' => $place->getZipCode(), + ]; + } + } + $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_graph.html.twig', [ + 'stats' => $stats, + 'theme' => $theme, + 'theme_label' => $themes[$theme], + 'count_data' => json_encode($countData), + 'completion_data' => json_encode($completionData), + 'icons' => \App\Service\FollowUpService::getFollowUpIcons(), + 'geojson' => json_encode($geojson), + 'overpass_query' => $overpass_query, + 'josm_url' => $josm_url, + 'center' => $center, + 'maptiler_token' => $_ENV['MAPTILER_TOKEN'] ?? null, + 'completion_tags' => \App\Service\FollowUpService::getFollowUpCompletionTags(), + 'followup_labels' => \App\Service\FollowUpService::getFollowUpThemes(), + ]); + } } diff --git a/src/Controller/FollowUpController.php b/src/Controller/FollowUpController.php index 2e5f023f..186297ac 100644 --- a/src/Controller/FollowUpController.php +++ b/src/Controller/FollowUpController.php @@ -117,6 +117,7 @@ class FollowUpController extends AbstractController 'followup_labels' => FollowUpService::getFollowUpThemes(), 'followup_icons' => FollowUpService::getFollowUpIcons(), 'followup_overpass' => FollowUpService::getFollowUpOverpassQueries(), + 'completion_tags' => FollowUpService::getFollowUpCompletionTags(), ]); } diff --git a/src/Service/FollowUpService.php b/src/Service/FollowUpService.php index 4b0b3b91..441c7389 100644 --- a/src/Service/FollowUpService.php +++ b/src/Service/FollowUpService.php @@ -92,196 +92,46 @@ class FollowUpService $em->clear(); } - // Suivi de la complétion personnalisé (exemples) + // Suivi de la complétion basé sur les tags attendus + $completionTags = self::getFollowUpCompletionTags(); + $expectedTags = $completionTags[$type] ?? []; $completed = []; - if ($type === 'fire_hydrant') { - $completed = array_filter($data['objects'], function($el) { + $partialCompletions = []; + if (!empty($expectedTags)) { + foreach ($data['objects'] as $el) { $tags = $el['tags'] ?? []; - // Considérer comme complet si au moins un critère supplémentaire est rempli - return !empty($tags['ref'] ?? null) - || !empty($tags['name'] ?? null) - || !empty($tags['operator'] ?? null) - || !empty($tags['colour'] ?? null); - }); - } elseif ($type === 'charging_station') { - $completed = array_filter($data['objects'], function($el) { - $tags = $el['tags'] ?? []; - return !empty($tags['charging_station:output'] ?? null) - || !empty($tags['capacity'] ?? null) - || !empty($tags['name'] ?? null) - || !empty($tags['operator'] ?? null); - }); - } elseif ($type === 'toilets') { - $completed = array_filter($data['objects'], function($el) { - $tags = $el['tags'] ?? []; - return ($tags['wheelchair'] ?? null) === 'yes' - || !empty($tags['name'] ?? null) - || !empty($tags['operator'] ?? null) - || !empty($tags['access'] ?? null); - }); - } elseif ($type === 'bus_stop') { - $completed = array_filter($data['objects'], function($el) { - $tags = $el['tags'] ?? []; - // Considérer comme complet si au moins un de ces critères est rempli - return !empty($tags['shelter'] ?? null) - || !empty($tags['name'] ?? null) - || !empty($tags['operator'] ?? null) - || !empty($tags['network'] ?? null); - }); - } elseif ($type === 'defibrillator') { - $completed = array_filter($data['objects'], function($el) { - $tags = $el['tags'] ?? []; - // Considérer comme complet si au moins un de ces critères est rempli - return !empty($tags['indoor'] ?? null) - || !empty($tags['name'] ?? null) - || !empty($tags['operator'] ?? null) - || !empty($tags['access'] ?? null); - }); - } elseif ($type === 'camera') { - $completed = array_filter($data['objects'], function($el) { - $tags = $el['tags'] ?? []; - return !empty($tags['surveillance:type'] ?? null) - || !empty($tags['name'] ?? null) - || !empty($tags['operator'] ?? null); - }); - } elseif ($type === 'recycling') { - $completed = array_filter($data['objects'], function($el) { - $tags = $el['tags'] ?? []; - return !empty($tags['recycling_type'] ?? null) - || !empty($tags['name'] ?? null) - || !empty($tags['operator'] ?? null); - }); - } elseif ($type === 'substation') { - $completed = array_filter($data['objects'], function($el) { - $tags = $el['tags'] ?? []; - return !empty($tags['substation'] ?? null) - || !empty($tags['name'] ?? null) - || !empty($tags['operator'] ?? null); - }); - } elseif ($type === 'laboratory') { - $completed = array_filter($data['objects'], function($el) { - $tags = $el['tags'] ?? []; - return !empty($tags['website'] ?? null) - || !empty($tags['contact:website'] ?? null) - || !empty($tags['name'] ?? null) - || !empty($tags['phone'] ?? null); - }); - } elseif ($type === 'school') { - $completed = array_filter($data['objects'], function($el) { - $tags = $el['tags'] ?? []; - // Considérer comme complet si au moins un de ces critères est rempli - return !empty($tags['ref:UAI'] ?? null) - || !empty($tags['isced:level'] ?? null) - || !empty($tags['school:FR'] ?? null) - || !empty($tags['name'] ?? null) - || !empty($tags['operator'] ?? null); - }); - } elseif ($type === 'police') { - $completed = array_filter($data['objects'], function($el) { - $tags = $el['tags'] ?? []; - return !empty($tags['phone'] ?? null) - || !empty($tags['website'] ?? null) - || !empty($tags['name'] ?? null) - || !empty($tags['operator'] ?? null); - }); - } elseif ($type === 'healthcare') { - $completed = array_filter($data['objects'], function($el) { - $tags = $el['tags'] ?? []; - return !empty($tags['name'] ?? null) - || !empty($tags['contact:phone'] ?? null) - || !empty($tags['phone'] ?? null) - || !empty($tags['email'] ?? null) - || !empty($tags['contact:email'] ?? null) - || !empty($tags['operator'] ?? null); - }); - } elseif ($type === 'bicycle_parking') { - $completed = array_filter($data['objects'], function($el) { - $tags = $el['tags'] ?? []; - return !empty($tags['capacity'] ?? null) - || !empty($tags['covered'] ?? null) - || !empty($tags['name'] ?? null) - || !empty($tags['operator'] ?? null); - }); - } elseif ($type === 'advertising_board') { - $completed = array_filter($data['objects'], function($el) { - $tags = $el['tags'] ?? []; - return !empty($tags['operator'] ?? null) - || !empty($tags['contact:phone'] ?? null) - || !empty($tags['name'] ?? null); - }); - } elseif ($type === 'building') { - $completed = array_filter($data['objects'], function($el) { - $tags = $el['tags'] ?? []; - return !empty($tags['name'] ?? null) - || !empty($tags['ref'] ?? null) - || !empty($tags['operator'] ?? null); - }); - } elseif ($type === 'email') { - $completed = array_filter($data['objects'], function($el) { - $tags = $el['tags'] ?? []; - return !empty($tags['name'] ?? null) - || !empty($tags['phone'] ?? null) - || !empty($tags['operator'] ?? null); - }); - } elseif ($type === 'bench') { - $completed = array_filter($data['objects'], function($el) { - $tags = $el['tags'] ?? []; - return !empty($tags['material'] ?? null) - || !empty($tags['backrest'] ?? null) - || !empty($tags['name'] ?? null) - || !empty($tags['operator'] ?? null); - }); - } elseif ($type === 'waste_basket') { - $completed = array_filter($data['objects'], function($el) { - $tags = $el['tags'] ?? []; - return !empty($tags['waste'] ?? null) - || !empty($tags['recycling_type'] ?? null) - || !empty($tags['name'] ?? null) - || !empty($tags['operator'] ?? null); - }); - } elseif ($type === 'street_lamp') { - $completed = array_filter($data['objects'], function($el) { - $tags = $el['tags'] ?? []; - return !empty($tags['lamp_type'] ?? null) - || !empty($tags['height'] ?? null) - || !empty($tags['name'] ?? null) - || !empty($tags['operator'] ?? null); - }); - } elseif ($type === 'drinking_water') { - $completed = array_filter($data['objects'], function($el) { - $tags = $el['tags'] ?? []; - return !empty($tags['covered'] ?? null) - || !empty($tags['ref'] ?? null) - || !empty($tags['name'] ?? null) - || !empty($tags['operator'] ?? null); - }); - } elseif ($type === 'tree') { - $completed = array_filter($data['objects'], function($el) { - $tags = $el['tags'] ?? []; - $hasTaxonomy = !empty($tags['species'] ?? null) - || !empty($tags['genus'] ?? null) - || !empty($tags['taxon'] ?? null) - || !empty($tags['taxon:binomial'] ?? null); - $hasLeaf = !empty($tags['leaf_type'] ?? null) - || !empty($tags['leaf_cycle'] ?? null) - || !empty($tags['leaf_shape'] ?? null) - || !empty($tags['leaf_color'] ?? null) - || !empty($tags['leaf_fall'] ?? null); - return $hasTaxonomy && $hasLeaf; - }); - } elseif ($type === 'power_pole') { - $completed = array_filter($data['objects'], function($el) { - $tags = $el['tags'] ?? []; - return !empty($tags['ref'] ?? null) - || !empty($tags['material'] ?? null) - || !empty($tags['height'] ?? null) - || !empty($tags['operator'] ?? null); - }); + $filled = 0; + foreach ($expectedTags as $tag) { + if ($tag === 'phone') { + if (!empty($tags['phone'] ?? null) || !empty($tags['contact:phone'] ?? null)) { + $filled++; + } + } elseif ($tag === 'email') { + if (!empty($tags['email'] ?? null) || !empty($tags['contact:email'] ?? null)) { + $filled++; + } + } else { + if (!empty($tags[$tag] ?? null)) { + $filled++; + } + } + } + $percent = count($expectedTags) > 0 ? ($filled / count($expectedTags)) : 0; + $partialCompletions[] = $percent; + if ($percent === 1.0) { + $completed[] = $el; + } + } + } + // ... fallback pour les types sans tags attendus + else { + $completed = []; + $partialCompletions = array_fill(0, count($data['objects']), 0); } if ($type === 'places') { $completion = $stats->getCompletionPercent(); } else { - $completion = count($data['objects']) > 0 ? round(count($completed) / count($data['objects']) * 100) : 0; + $completion = count($partialCompletions) > 0 ? round(array_sum($partialCompletions) / count($partialCompletions) * 100) : 0; } $followupCompletion = new CityFollowUp(); $followupCompletion->setName($type . '_completion') @@ -619,4 +469,36 @@ class FollowUpService // Si aucune mesure n'est disponible, retourner null return null; } + + /** + * Retourne pour chaque thématique la liste des tags attendus pour la complétion + */ + public static function getFollowUpCompletionTags(): array + { + return [ + 'fire_hydrant' => ['ref', 'colour'], + 'charging_station' => ['charging_station:output', 'capacity'], + 'toilets' => ['wheelchair', 'access'], + 'bus_stop' => ['shelter', 'network'], + 'defibrillator' => ['indoor', 'access'], + 'camera' => ['surveillance:type'], + 'recycling' => ['recycling_type'], + 'substation' => ['substation'], + 'laboratory' => ['website', 'contact:website', 'name', 'phone'], + 'school' => ['ref:UAI', 'isced:level', 'school:FR'], + 'police' => ['phone', 'website'], + 'healthcare' => ['name', 'contact:phone', 'phone', 'email', 'contact:email'], + 'bicycle_parking' => ['capacity', 'covered'], + 'advertising_board' => ['operator', 'contact:phone'], + 'building' => ['name', 'ref'], + 'email' => ['name', 'phone'], + 'bench' => ['material', 'backrest'], + 'waste_basket' => ['waste', 'recycling_type'], + 'street_lamp' => ['lamp_type', 'height'], + 'drinking_water' => ['covered', 'ref'], + 'tree' => ['species', 'leaf_type', 'leaf_cycle'], + 'power_pole' => ['ref', 'material'], + 'places' => ['name', 'address', 'opening_hours', 'website', 'phone', 'wheelchair', 'siret'], + ]; + } } \ No newline at end of file diff --git a/templates/admin/_followup_completion_tags.html.twig b/templates/admin/_followup_completion_tags.html.twig new file mode 100644 index 00000000..b7cd2b7f --- /dev/null +++ b/templates/admin/_followup_completion_tags.html.twig @@ -0,0 +1,29 @@ +{# + Template partiel pour afficher les critères de complétion par thématique + Variables attendues : + - completion_tags : tableau [type => [tags...]] + - followup_labels : tableau [type => label] + - all_types : liste des types (clés) +#} +
+ {% for type in all_types %} +
+ {{ followup_labels[type]|default(type|capitalize) }} + +
+
+ Critères de complétion attendus : +
    + {% for tag in completion_tags[type] ?? [] %} +
  • {{ tag }}
  • + {% else %} +
  • Aucun critère dĂ©fini
  • + {% endfor %} +
+
+
+
+ {% endfor %} +
\ No newline at end of file diff --git a/templates/admin/followup_graph.html.twig b/templates/admin/followup_graph.html.twig index fecf8ce1..a15077d2 100644 --- a/templates/admin/followup_graph.html.twig +++ b/templates/admin/followup_graph.html.twig @@ -59,6 +59,11 @@ Retour Ă  la fiche ville +{% include 'admin/_followup_completion_tags.html.twig' with { + 'completion_tags': completion_tags, + 'followup_labels': followup_labels, + 'all_types': followup_labels|keys +} %} {% endblock %} {% block javascripts %} diff --git a/templates/admin/followup_theme_graph.html.twig b/templates/admin/followup_theme_graph.html.twig index a442537e..4f479d98 100644 --- a/templates/admin/followup_theme_graph.html.twig +++ b/templates/admin/followup_theme_graph.html.twig @@ -228,6 +228,23 @@
+ + {% if completion_tags is defined and completion_tags[theme] is defined %} +
+
+ Critères de complétion attendus pour ce thème +
+
+
    + {% for tag in completion_tags[theme] %} +
  • {{ tag }}
  • + {% else %} +
  • Aucun critère dĂ©fini
  • + {% endfor %} +
+
+
+ {% endif %} {% endblock %} diff --git a/templates/admin/stats.html.twig b/templates/admin/stats.html.twig index e8030bec..5361bf28 100644 --- a/templates/admin/stats.html.twig +++ b/templates/admin/stats.html.twig @@ -339,6 +339,28 @@ {% endfor %} +
+ {% for type in all_types %} +
+ {{ followup_labels[type]|default(type|capitalize) }} + +
+
+ Critères de complétion attendus : +
    + {% for tag in completion_tags[type] ?? [] %} +
  • {{ tag }}
  • + {% else %} +
  • Aucun critère dĂ©fini
  • + {% endfor %} +
+
+
+
+ {% endfor %} +