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 @@ {{ commerce.address }} {{ commerce.housenumber }} - {{ commerce.street }} + + {% if commerce.street %} + {{ commerce.street }} + {% else %} + (inconnue) + {% endif %} + + {# {{ commerce.street }} #} + {{ commerce.website }} {{ commerce.wheelchair }} {{ commerce.note }} diff --git a/templates/admin/stats/table-head.html.twig b/templates/admin/stats/table-head.html.twig index 17bd360a..919f5a16 100644 --- a/templates/admin/stats/table-head.html.twig +++ b/templates/admin/stats/table-head.html.twig @@ -19,7 +19,7 @@ - Adresse ({{ stats.getAvecAdresse() }} / {{ stats.places|length }}) + Adresse ({{ stats.getAvecAdresse is defined ? stats.getAvecAdresse : 0 }} / {{ stats.places|length }}) @@ -31,17 +31,17 @@ - Site web ({{ stats.getAvecSite() }} / {{ stats.places|length }}) + Site web ({{ stats.getAvecSite is defined ? stats.getAvecSite : 0 }} / {{ stats.places|length }}) Accès PMR - ({{ stats.getAvecAccessibilite() }} / {{ stats.places|length }}) + ({{ stats.getAvecAccessibilite is defined ? stats.getAvecAccessibilite : 0 }} / {{ stats.places|length }}) - Note ? ({{ stats.getAvecNote() }} / {{ stats.places|length }}) + Note ? ({{ stats.getAvecNote is defined ? stats.getAvecNote : 0 }} / {{ stats.places|length }}) Texte de la note diff --git a/templates/public/place/row.html.twig b/templates/public/place/row.html.twig index c0c4da02..8947dd57 100644 --- a/templates/public/place/row.html.twig +++ b/templates/public/place/row.html.twig @@ -10,6 +10,12 @@ {{ place.modifiedDate | date('Y-m-d H:i:s') }} {{ place.lastContactAttemptDate | date('Y-m-d H:i:s') }} {{ place.modifiedDate | date('Y-m-d H:i:s') }} + {% if place.street %} + {{ place.street }} + {% else %} + (inconnue) + {% endif %} + {{ place.zipCode }} diff --git a/templates/public/stats_evolutions.html.twig b/templates/public/stats_evolutions.html.twig new file mode 100644 index 00000000..c641269a --- /dev/null +++ b/templates/public/stats_evolutions.html.twig @@ -0,0 +1,141 @@ +{% extends 'base.html.twig' %} + +{% block title %}Évolutions des objets - {{ stats.name }}{% endblock %} + +{% block body %} +
+

Évolutions des objets à {{ stats.name }} ({{ stats.zone }})

+ Retour aux stats +
+
+ Variation des décomptes d'objets par type +
+
+ + + + + + {% for p in periods %} + + {% endfor %} + + + + {% for type, evo in evolutions %} + + + + {% for p in periods %} + + {% endfor %} + + {% else %} + + {% endfor %} + +
TypeDécompte actuelÉvolution sur {{ p }}
{{ type }}{{ evo.now }}{% if evo[p] is not null %}{{ evo[p] > 0 ? '+' ~ evo[p] : evo[p] }}{% else %}-{% endif %}
Aucune donnée d'évolution trouvée.
+
+
+
+
+

Lieux modifiés cette semaine

+ {% if places_7j|length > 0 %} +
    + {% for place in places_7j %} +
  • + + + + {{ place.name ?: '(sans nom)' }} + +
    + + {% if place.street %} + {{ place.street }} + {% else %} + (inconnue) + {% endif %} + {{ place.housenumber }} + +
    + + {{ place.getModifiedDate() ? place.getModifiedDate()|date('Y-m-d H:i') : '' }} + + +
  • + {% endfor %} +
+ {% else %} +

Aucun lieu modifié dans les 7 derniers jours.

+ {% endif %} +
+
+

Ce mois-ci

+ {% if places_30j|length > 0 %} +
    + {% for place in places_30j %} +
  • + + + + {{ place.name ?: '(sans nom)' }} + +
    + + {% if place.street %} + {{ place.street }} + {% else %} + (inconnue) + {% endif %} + {{ place.housenumber }} + +
    + + {{ place.getModifiedDate() ? place.getModifiedDate()|date('Y-m-d H:i') : '' }} + + +
  • + {% endfor %} +
+ {% else %} +

Aucun lieu modifié dans les 30 derniers jours.

+ {% endif %} +
+
+

6 derniers mois

+ {% if places_6mois|length > 0 %} +
    + {% for place in places_6mois %} +
  • + + + + {{ place.name ?: '(sans nom)' }} + +
    + + {% if place.street %} + {{ place.street }} + {% else %} + (inconnue) + {% endif %} + {{ place.housenumber }} + +
    + + {{ place.getModifiedDate() ? place.getModifiedDate()|date('Y-m-d H:i') : '' }} + + +
  • + {% endfor %} +
+ {% else %} +

Aucun lieu modifié dans les 6 derniers mois.

+ {% endif %} +
+
+ + +
+{% endblock %} \ No newline at end of file diff --git a/templates/public/street.html.twig b/templates/public/street.html.twig new file mode 100644 index 00000000..828fff58 --- /dev/null +++ b/templates/public/street.html.twig @@ -0,0 +1,109 @@ +{% extends 'base.html.twig' %} + +{# {% block title %}Suivi de la rue {{ street }} à {{ city }}{% endblock %} #} + +{% block stylesheets %} + {{ parent() }} + + +{% endblock %} + +{% block body %} +
+
+
+

Rue {{ street|e('html') }} à {{ city }}

+ Voir les statistiques de la ville +
+ Retour à l'accueil +
+ +
+
+

Répartition de la complétion

+ +
+
+

Carte des lieux

+
+ Si il manque des lieux dans cette rue c'est parce que les objets n'ont pas d'attribut "addr:street" et "addr:housenumber" +
+
+ +

Lieux de la rue

+
+ + {% include 'admin/stats/table-head.html.twig' with {stats: {places: places, getAvecAdresse: 0, getAvecSite: 0, getAvecAccessibilite: 0, getAvecNote: 0}} %} + + {% for commerce in places %} + {% include 'admin/stats/row.html.twig' with {commerce: commerce, stats: {population: 0, places: places}} %} + {% else %} + + {% endfor %} + +
Aucun lieu trouvé pour cette rue.
+
+
+{% endblock %} + +{% block javascripts %} + {{ parent() }} + + + +{% endblock %} \ No newline at end of file