diff --git a/src/Controller/PublicController.php b/src/Controller/PublicController.php index 3b89391c..69b8d0cb 100644 --- a/src/Controller/PublicController.php +++ b/src/Controller/PublicController.php @@ -716,4 +716,128 @@ class PublicController extends AbstractController { return $this->render('public/faq.html.twig'); } + + #[Route('/ville/{cityId}/rue/{streetName}', name: 'app_public_street')] + public function streetView(string $cityId, string $streetName): Response + { + $cityStats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $cityId]); + if (!$cityStats) { + throw $this->createNotFoundException('Ville non trouvée'); + } + // Décodage du nom de la rue depuis l'URL + $streetName = urldecode($streetName); + // On récupère tous les lieux dont le stats.zone correspond à cityId et la rue correspondante + $places = $this->entityManager->getRepository(Place::class)->createQueryBuilder('p') + ->leftJoin('p.stats', 's') + ->where('s.zone = :cityId') + ->andWhere('p.street = :streetName') + ->setParameter('cityId', $cityId) + ->setParameter('streetName', $streetName) + ->getQuery() + ->getResult(); + // Conversion des entités Place en tableau associatif pour le JS + $placesArray = array_map(fn($place) => $place->toArray(), $places); + + // Préparer la répartition de complétion pour le graphe + $completionBuckets = [ + '0-20%' => 0, + '20-40%' => 0, + '40-60%' => 0, + '60-80%' => 0, + '80-100%' => 0 + ]; + foreach ($places as $place) { + $c = $place->getCompletionPercentage(); + if ($c < 20) $completionBuckets['0-20%']++; + elseif ($c < 40) $completionBuckets['20-40%']++; + elseif ($c < 60) $completionBuckets['40-60%']++; + elseif ($c < 80) $completionBuckets['60-80%']++; + else $completionBuckets['80-100%']++; + } + + return $this->render('public/street.html.twig', [ + 'city' => $cityStats->getName(), + 'city_id' => $cityId, + 'street' => $streetName, + 'places' => $places, // objets Place pour le HTML + 'places_js' => $placesArray, // pour le JS si besoin + 'completion_buckets' => $completionBuckets, + 'completion_buckets_values' => array_values($completionBuckets), + 'stats_url' => $this->generateUrl('app_admin_stats', ['insee_code' => $cityId]), + 'maptiler_token' => $_ENV['MAPTILER_TOKEN'] ?? null + ]); + } + + #[Route('/stats/{insee_code}/evolutions', name: 'app_public_stats_evolutions')] + public function statsEvolutions(string $insee_code): Response + { + $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]); + if (!$stats) { + throw $this->createNotFoundException('Ville non trouvée'); + } + $now = new \DateTime(); + $periods = [ + '7j' => (clone $now)->modify('-7 days'), + '30j' => (clone $now)->modify('-30 days'), + '6mois' => (clone $now)->modify('-6 months'), + ]; + $followups = $stats->getCityFollowUps(); + $types = []; + foreach ($followups as $fu) { + $name = $fu->getName(); + if (str_ends_with($name, '_count')) { + $type = substr($name, 0, -6); + $types[$type][] = $fu; + } + } + $evolutions = []; + foreach ($types as $type => $fus) { + usort($fus, fn($a, $b) => $a->getDate() <=> $b->getDate()); + $latest = end($fus); + $evolutions[$type] = [ + 'now' => $latest ? $latest->getMeasure() : null + ]; + foreach ($periods as $label => $date) { + $past = null; + foreach ($fus as $fu) { + if ($fu->getDate() >= $date) { + $past = $fu->getMeasure(); + break; + } + } + $evolutions[$type][$label] = $past !== null && $latest ? $latest->getMeasure() - $past : null; + } + } + // Grouper les lieux par date de modification + $places = $stats->getPlaces(); + $now = new \DateTime(); + $places_7j = []; + $places_30j = []; + $places_6mois = []; + foreach ($places as $place) { + $mod = $place->getModifiedDate(); + if (!$mod) continue; + $diff = $now->diff($mod); + $days = (int)$diff->format('%a'); + if ($days <= 7) { + $places_7j[] = $place; + } elseif ($days <= 30) { + $places_30j[] = $place; + } elseif ($days <= 180) { + $places_6mois[] = $place; + } + } + // Tri décroissant par date + usort($places_7j, fn($a, $b) => $b->getModifiedDate() <=> $a->getModifiedDate()); + usort($places_30j, fn($a, $b) => $b->getModifiedDate() <=> $a->getModifiedDate()); + usort($places_6mois, fn($a, $b) => $b->getModifiedDate() <=> $a->getModifiedDate()); + return $this->render('public/stats_evolutions.html.twig', [ + 'stats' => $stats, + 'evolutions' => $evolutions, + 'periods' => array_keys($periods), + 'places_7j' => $places_7j, + 'places_30j' => $places_30j, + 'places_6mois' => $places_6mois, + ]); + } } diff --git a/src/Entity/Place.php b/src/Entity/Place.php index 6838272d..9c7af8cb 100644 --- a/src/Entity/Place.php +++ b/src/Entity/Place.php @@ -752,4 +752,17 @@ class Place $this->emailContent = $emailContent; return $this; } + + public function toArray(): array + { + return [ + 'name' => $this->getName(), + 'mainTag' => $this->getMainTag(), + 'housenumber' => $this->getHousenumber(), + 'street' => $this->getStreet(), + 'completionPercentage' => $this->getCompletionPercentage(), + 'lat' => $this->getLat(), + 'lon' => $this->getLon(), + ]; + } } diff --git a/templates/admin/stats.html.twig b/templates/admin/stats.html.twig index 5361bf28..7a1b5128 100644 --- a/templates/admin/stats.html.twig +++ b/templates/admin/stats.html.twig @@ -133,6 +133,9 @@ + + Évolutions des objets + {% if stats.population %} diff --git a/templates/admin/stats/row.html.twig b/templates/admin/stats/row.html.twig index f87966c3..42d543bf 100644 --- a/templates/admin/stats/row.html.twig +++ b/templates/admin/stats/row.html.twig @@ -79,7 +79,15 @@
Type | +Décompte actuel | + {% for p in periods %} +Évolution sur {{ p }} | + {% endfor %} +||
---|---|---|---|---|
{{ type }} | +{{ evo.now }} | + {% for p in periods %} +{% if evo[p] is not null %}{{ evo[p] > 0 ? '+' ~ evo[p] : evo[p] }}{% else %}-{% endif %} | + {% endfor %} +||
Aucune donnée d'évolution trouvée. |
Aucun lieu modifié dans les 7 derniers jours.
+ {% endif %} +Aucun lieu modifié dans les 30 derniers jours.
+ {% endif %} +Aucun lieu modifié dans les 6 derniers mois.
+ {% endif %} +Aucun lieu trouvé pour cette rue. |