diff --git a/src/Controller/PublicController.php b/src/Controller/PublicController.php index 8b1efde2..727afea4 100644 --- a/src/Controller/PublicController.php +++ b/src/Controller/PublicController.php @@ -1476,12 +1476,34 @@ class PublicController extends AbstractController throw $this->createNotFoundException('Ville non trouvée'); } + // Vérifier si le dernier labourage date de plus de 23h + $lastLabourage = $stats->getDateLabourageDone(); + $shouldRefresh = false; + + if ($lastLabourage) { + $now = new \DateTime(); + $diff = $now->diff($lastLabourage); + // Vérifier si plus de 23 heures se sont écoulées + if ($diff->days > 0 || ($diff->days === 0 && $diff->h >= 23)) { + $shouldRefresh = true; + } + } else { + // Si jamais de labourage, on rafraîchit quand même + $shouldRefresh = true; + } + + // Rafraîchir tous les ZonePlaces si nécessaire + if ($shouldRefresh) { + $this->followUpService->refreshAllZonePlaces($stats, $this->motocultrice, $this->entityManager); + } + // Récupérer tous les ZonePlaces de cette ville $zonePlaces = $stats->getZonePlaces(); $changesByDate = []; $followupLabels = \App\Service\FollowUpService::getFollowUpThemes(); $followupIcons = \App\Service\FollowUpService::getFollowUpIcons(); $thirtyDaysAgo = new \DateTime('-30 days'); + $uniqueUsers = []; foreach ($zonePlaces as $zp) { $theme = $zp->getTheme(); @@ -1499,6 +1521,11 @@ class PublicController extends AbstractController $changesByDate[$date]['deletions'][$theme] = []; } $changesByDate[$date]['deletions'][$theme][] = $obj; + + // Collecter les utilisateurs uniques + if (!empty($obj['user'])) { + $uniqueUsers[$obj['user']] = true; + } } } } @@ -1519,6 +1546,11 @@ class PublicController extends AbstractController $changesByDate[$date]['creations'][$theme] = []; } $changesByDate[$date]['creations'][$theme][] = $obj; + + // Collecter les utilisateurs uniques + if (!empty($obj['user'])) { + $uniqueUsers[$obj['user']] = true; + } } } catch (\Exception $e) { // Ignorer les timestamps invalides @@ -1531,11 +1563,16 @@ class PublicController extends AbstractController // Trier par date décroissante krsort($changesByDate); + // Trier les utilisateurs uniques par ordre alphabétique + $uniqueUsersList = array_keys($uniqueUsers); + sort($uniqueUsersList); + return $this->render('public/zone_places_history.html.twig', [ 'stats' => $stats, 'changesByDate' => $changesByDate, 'followup_labels' => $followupLabels, 'followup_icons' => $followupIcons, + 'unique_users' => $uniqueUsersList, ]); } diff --git a/src/Service/FollowUpService.php b/src/Service/FollowUpService.php index c1000dcc..dd089576 100644 --- a/src/Service/FollowUpService.php +++ b/src/Service/FollowUpService.php @@ -628,4 +628,100 @@ class FollowUpService $em->persist($zonePlaces); } + + /** + * Rafraîchit tous les ZonePlaces d'une ville en récupérant les données Overpass + */ + public function refreshAllZonePlaces(Stats $stats, Motocultrice $motocultrice, EntityManagerInterface $em): void + { + $insee_code = $stats->getZone(); + $elements = $motocultrice->followUpCity($insee_code) ?? []; + $themes = self::getFollowUpThemes(); + $now = new \DateTime(); + $zonePlacesRepository = $em->getRepository(ZonePlaces::class); + + foreach ($themes as $type => $label) { + // On ne gère pas les ZonePlaces pour le thème 'places' + if ($type === 'places') { + continue; + } + + // Filtrer les objets selon le thème + $themeObjects = $this->filterObjectsByThemeForRefresh($elements, $type); + + // Mettre à jour même si vide pour détecter les suppressions + $this->updateZonePlacesForTheme($stats, $type, $themeObjects, $zonePlacesRepository, $em, $now); + } + + $em->flush(); + } + + /** + * Filtre les objets Overpass selon le thème (pour le rafraîchissement) + */ + private function filterObjectsByThemeForRefresh(array $elements, string $theme): array + { + if ($theme === 'fire_hydrant') { + return array_filter($elements, fn($el) => ($el['tags']['emergency'] ?? null) === 'fire_hydrant') ?? []; + } elseif ($theme === 'charging_station') { + return array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'charging_station') ?? []; + } elseif ($theme === 'toilets') { + return array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'toilets') ?? []; + } elseif ($theme === 'bus_stop') { + return array_filter($elements, fn($el) => ($el['tags']['highway'] ?? null) === 'bus_stop') ?? []; + } elseif ($theme === 'defibrillator') { + return array_filter($elements, fn($el) => ($el['tags']['emergency'] ?? null) === 'defibrillator') ?? []; + } elseif ($theme === 'camera') { + return array_filter($elements, fn($el) => ($el['tags']['man_made'] ?? null) === 'surveillance') ?? []; + } elseif ($theme === 'recycling') { + return array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'recycling') ?? []; + } elseif ($theme === 'substation') { + return array_filter($elements, fn($el) => ($el['tags']['power'] ?? null) === 'substation') ?? []; + } elseif ($theme === 'laboratory') { + return array_filter($elements, fn($el) => ($el['tags']['healthcare'] ?? null) === 'laboratory') ?? []; + } elseif ($theme === 'school') { + return array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'school') ?? []; + } elseif ($theme === 'police') { + return array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'police') ?? []; + } elseif ($theme === 'healthcare') { + return array_filter($elements, function ($el) { + return isset($el['tags']['healthcare']) + || ($el['tags']['amenity'] ?? null) === 'doctors' + || ($el['tags']['amenity'] ?? null) === 'pharmacy' + || ($el['tags']['amenity'] ?? null) === 'hospital' + || ($el['tags']['amenity'] ?? null) === 'clinic' + || ($el['tags']['amenity'] ?? null) === 'social_facility'; + }) ?? []; + } elseif ($theme === 'bicycle_parking') { + return array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'bicycle_parking') ?? []; + } elseif ($theme === 'advertising_board') { + return array_filter($elements, fn($el) => ($el['tags']['advertising'] ?? null) === 'board' && ($el['tags']['message'] ?? null) === 'political') ?? []; + } elseif ($theme === 'building') { + return array_filter($elements, fn($el) => ($el['type'] ?? null) === 'way' && !empty($el['tags']['building'])) ?? []; + } elseif ($theme === 'email') { + return array_filter($elements, fn($el) => !empty($el['tags']['email'] ?? null) || !empty($el['tags']['contact:email'] ?? null)) ?? []; + } elseif ($theme === 'bench') { + return array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'bench') ?? []; + } elseif ($theme === 'waste_basket') { + return array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'waste_basket') ?? []; + } elseif ($theme === 'street_lamp') { + return array_filter($elements, fn($el) => ($el['tags']['highway'] ?? null) === 'street_lamp') ?? []; + } elseif ($theme === 'drinking_water') { + return array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'drinking_water') ?? []; + } elseif ($theme === 'tree') { + return array_filter($elements, fn($el) => ($el['tags']['natural'] ?? null) === 'tree') ?? []; + } elseif ($theme === 'power_pole') { + return array_filter($elements, fn($el) => ($el['tags']['power'] ?? null) === 'pole') ?? []; + } elseif ($theme === 'manhole') { + return array_filter($elements, fn($el) => ($el['tags']['manhole'] ?? null) === 'manhole') ?? []; + } elseif ($theme === 'little_free_library') { + return array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'public_bookcase') ?? []; + } elseif ($theme === 'playground') { + return array_filter($elements, fn($el) => ($el['tags']['leisure'] ?? null) === 'playground') ?? []; + } elseif ($theme === 'restaurant') { + return array_filter($elements, fn($el) => ($el['tags']['amenity'] ?? null) === 'restaurant') ?? []; + } + + return []; + } } diff --git a/templates/public/zone_places_history.html.twig b/templates/public/zone_places_history.html.twig index d6471cb6..e7ad6f6e 100644 --- a/templates/public/zone_places_history.html.twig +++ b/templates/public/zone_places_history.html.twig @@ -26,20 +26,80 @@ Aucun changement enregistré pour cette ville. {% else %} + {# Filtres #} +
+
+
Filtres
+
+
+
+
+ +
+ + + +
+
+
+
+
+ +
+ + {% for theme, label in followup_labels %} + {% if theme != 'places' %} + {% set themeIcon = followup_icons[theme]|default('bi-question-circle') %} + + {% endif %} + {% endfor %} +
+
+
+ {% if unique_users is defined and unique_users|length > 0 %} +
+
+ +
+ + {% for user in unique_users %} + + {% endfor %} +
+
+
+ {% endif %} +
+
+ {% for date, changes in changesByDate %} -
+

{{ date|date('d/m/Y') }}

{# Suppressions #} {% if changes.deletions is not empty %} -
+

Suppressions

{% for theme, objects in changes.deletions %} {% set themeLabel = followup_labels[theme]|default(theme|capitalize) %} {% set themeIcon = followup_icons[theme]|default('bi-question-circle') %} -
+
{{ themeLabel }} ({{ objects|length }} suppression{{ objects|length > 1 ? 's' : '' }})
@@ -57,7 +117,7 @@ {% for obj in objects %} - + @@ -71,9 +131,9 @@ Inconnu {% endif %} -
{{ themeLabel }} {{ obj.type|upper }} {{ obj.id }} + {% if obj.timestamp %} - {{ obj.timestamp }} + {{ obj.timestamp }} {% else %} N/A {% endif %} @@ -124,12 +184,12 @@ {# Créations #} {% if changes.creations is not empty %} -
+

Créations/Modifications

{% for theme, objects in changes.creations %} {% set themeLabel = followup_labels[theme]|default(theme|capitalize) %} {% set themeIcon = followup_icons[theme]|default('bi-question-circle') %} -
+
{{ themeLabel }} ({{ objects|length }} objet{{ objects|length > 1 ? 's' : '' }})
@@ -146,8 +206,8 @@ {% for obj in objects %} - - + + -
{{ theme }}
{{ themeLabel }} {{ obj.type|upper }} {{ obj.id }} @@ -160,9 +220,9 @@ Inconnu {% endif %} + {% if obj.timestamp %} - {{ obj.timestamp }} + {{ obj.timestamp }} {% else %} N/A {% endif %} @@ -219,3 +279,176 @@ {% endblock %} +{% block javascripts %} + {{ parent() }} + +{% endblock %} +