From b61fa6a28725c170ff80e8c2f5c7193c21d0c5b4 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Tue, 17 Jun 2025 19:38:44 +0200 Subject: [PATCH] add history on stats --- assets/styles/app.css | 5 + migrations/Version20250617165738.php | 59 +++++++ public/js/utils.js | 12 +- src/Controller/AdminController.php | 25 ++- src/Entity/Stats.php | 80 ++++++++-- src/Entity/StatsHistory.php | 90 +++++++++++ templates/admin/stats.html.twig | 194 ++++++++++++++++++++++-- templates/admin/stats_history.html.twig | 62 ++++++++ templates/public/dashboard.html.twig | 5 +- templates/public/home.html.twig | 5 +- translations/messages.fr.yaml | 2 +- 11 files changed, 502 insertions(+), 37 deletions(-) create mode 100644 migrations/Version20250617165738.php create mode 100644 templates/admin/stats_history.html.twig diff --git a/assets/styles/app.css b/assets/styles/app.css index 954bfb9..d66ce9d 100644 --- a/assets/styles/app.css +++ b/assets/styles/app.css @@ -148,4 +148,9 @@ img { .mb-3 { margin-bottom: 1rem !important; } +} + +table tbody { + max-height: 700px; + overflow: auto; } \ No newline at end of file diff --git a/migrations/Version20250617165738.php b/migrations/Version20250617165738.php new file mode 100644 index 0000000..eff0168 --- /dev/null +++ b/migrations/Version20250617165738.php @@ -0,0 +1,59 @@ +addSql(<<<'SQL' + ALTER TABLE stats_history ADD names_count INT DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE stats_history ADD opening_hours_count INT DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE stats_history ADD website_count INT DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE stats_history ADD address_count INT DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE stats_history ADD siret_count INT DEFAULT NULL + SQL); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql(<<<'SQL' + ALTER TABLE stats_history DROP names_count + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE stats_history DROP opening_hours_count + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE stats_history DROP website_count + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE stats_history DROP address_count + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE stats_history DROP siret_count + SQL); + } +} diff --git a/public/js/utils.js b/public/js/utils.js index bc42570..a75b7f1 100644 --- a/public/js/utils.js +++ b/public/js/utils.js @@ -5,7 +5,7 @@ * @param {string} suggestionListId - ID de la liste des suggestions * @param {Function} onSelect - Callback appelé lors de la sélection d'une ville */ -export function setupCitySearch(inputId, suggestionListId, onSelect) { +function setupCitySearch(inputId, suggestionListId, onSelect) { const searchInput = document.getElementById(inputId); const suggestionList = document.getElementById(suggestionListId); @@ -75,12 +75,12 @@ export function setupCitySearch(inputId, suggestionListId, onSelect) { * @param {string} zipCode - Le code postal * @returns {string} L'URL de labourage */ -export function getLabourerUrl(zipCode) { +function getLabourerUrl(zipCode) { return `/admin/labourer/${zipCode}`; } // Fonction pour gérer la soumission du formulaire d'ajout de ville -export function handleAddCityFormSubmit(event) { +function handleAddCityFormSubmit(event) { event.preventDefault(); const form = event.target; const submitButton = form.querySelector('button[type="submit"]'); @@ -101,7 +101,7 @@ export function handleAddCityFormSubmit(event) { * @param {string} selector - Le sélecteur CSS pour cibler les cellules à colorer * @param {string} color - La couleur de base en format RGB (ex: '154, 205, 50') */ -export function colorizePercentageCells(selector, color = '154, 205, 50') { +function colorizePercentageCells(selector, color = '154, 205, 50') { document.querySelectorAll(selector).forEach(cell => { const percentage = parseInt(cell.textContent); if (!isNaN(percentage)) { @@ -116,7 +116,7 @@ export function colorizePercentageCells(selector, color = '154, 205, 50') { * @param {string} selector - Le sélecteur CSS pour cibler les cellules à colorer * @param {string} color - La couleur de base en format RGB (ex: '154, 205, 50') */ -export function colorizePercentageCellsRelative(selector, color = '154, 205, 50') { +function colorizePercentageCellsRelative(selector, color = '154, 205, 50') { // Récupérer toutes les cellules const cells = document.querySelectorAll(selector); @@ -145,7 +145,7 @@ export function colorizePercentageCellsRelative(selector, color = '154, 205, 50' * @param {number} [minFont=0.8] - Taille de police minimale en rem * @param {number} [maxFont=1.2] - Taille de police maximale en rem */ -export function adjustListGroupFontSize(selector, minFont = 0.8, maxFont = 1.2) { +function adjustListGroupFontSize(selector, minFont = 0.8, maxFont = 1.2) { const items = document.querySelectorAll(selector); const count = items.length; let fontSize = maxFont; diff --git a/src/Controller/AdminController.php b/src/Controller/AdminController.php index 0dafc3e..89782b9 100644 --- a/src/Controller/AdminController.php +++ b/src/Controller/AdminController.php @@ -8,6 +8,7 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; use App\Entity\Place; use App\Entity\Stats; +use App\Entity\StatsHistory; use App\Service\Motocultrice; use Doctrine\ORM\EntityManagerInterface; use function uuid_create; @@ -146,6 +147,8 @@ final class AdminController extends AbstractController $city = $this->motocultrice->get_city_osm_from_zip_code($insee_code); if (!$stats) { $stats = new Stats(); + $stats->setDateCreated(new \DateTime()); + $stats->setDateModified(new \DateTime()); $stats->setZone($insee_code) ->setPlacesCount(0) ->setAvecHoraires(0) @@ -234,15 +237,30 @@ final class AdminController extends AbstractController // Mettre à jour les statistiques finales $stats->computeCompletionPercent(); + if($stats->getDateCreated() == null) { + $stats->setDateCreated(new \DateTime()); + } + + $stats->setDateModified(new \DateTime()); + // Créer un historique des statistiques $statsHistory = new StatsHistory(); + $statsHistory->setDate(new \DateTime()) + ->setStats($stats); + $statsHistory->setPlacesCount($stats->getPlaces()->count()) + ->setOpeningHoursCount($stats->getAvecHoraires()) + ->setAddressCount($stats->getAvecAdresse()) + ->setWebsiteCount($stats->getAvecSite()) + ->setSiretCount($stats->getAvecSiret()) + // ->setAccessibiliteCount($stats->getAvecAccessibilite()) + // ->setNoteCount($stats->getAvecNote()) ->setCompletionPercent($stats->getCompletionPercent()) ->setStats($stats); $this->entityManager->persist($statsHistory); - + $this->entityManager->persist($stats); $this->entityManager->flush(); @@ -253,10 +271,11 @@ final class AdminController extends AbstractController $this->addFlash('success', $message); } catch (\Exception $e) { $this->addFlash('error', 'Erreur lors du labourage : ' . $e->getMessage()); + die(var_dump($e)); } - return $this->redirectToRoute('app_public_dashboard'); - // return $this->redirectToRoute('app_admin_stats', ['insee_code' => $insee_code]); + // return $this->redirectToRoute('app_public_dashboard'); + return $this->redirectToRoute('app_admin_stats', ['insee_code' => $insee_code]); } #[Route('/admin/delete/{id}', name: 'app_admin_delete')] diff --git a/src/Entity/Stats.php b/src/Entity/Stats.php index 70acfe5..4278af8 100644 --- a/src/Entity/Stats.php +++ b/src/Entity/Stats.php @@ -16,10 +16,10 @@ class Stats #[ORM\Column] private ?int $id = null; - #[ORM\Column(length: 255)] + #[ORM\Column(length: 255, type: Types::STRING, nullable: true)] private ?string $zone = null; // code insee de la zone - #[ORM\Column(type: Types::SMALLINT)] + #[ORM\Column(type: Types::INTEGER, nullable: true)] private ?int $completion_percent = null; /** @@ -29,27 +29,27 @@ class Stats private Collection $places; // nombre de commerces dans la zone - #[ORM\Column(type: Types::SMALLINT, nullable: true)] + #[ORM\Column(type: Types::INTEGER, nullable: true)] private ?int $places_count = null; // nombre de commerces avec horaires - #[ORM\Column(type: Types::SMALLINT, nullable: true)] + #[ORM\Column(type: Types::INTEGER, nullable: true)] private ?int $avec_horaires = null; // nombre de commerces avec adresse - #[ORM\Column(type: Types::SMALLINT, nullable: true)] + #[ORM\Column(type: Types::INTEGER, nullable: true)] private ?int $avec_adresse = null; // nombre de commerces avec site - #[ORM\Column(type: Types::SMALLINT, nullable: true)] + #[ORM\Column(type: Types::INTEGER, nullable: true)] private ?int $avec_site = null; // nombre de commerces avec accessibilité - #[ORM\Column(type: Types::SMALLINT, nullable: true)] + #[ORM\Column(type: Types::INTEGER, nullable: true)] private ?int $avec_accessibilite = null; // nombre de commerces avec note - #[ORM\Column(type: Types::SMALLINT, nullable: true)] + #[ORM\Column(type: Types::INTEGER, nullable: true)] private ?int $avec_note = null; #[ORM\Column(length: 255, nullable: true)] @@ -59,10 +59,10 @@ class Stats #[ORM\Column(type: Types::INTEGER, nullable: true)] private ?int $population = null; - #[ORM\Column(type: Types::SMALLINT, nullable: true)] + #[ORM\Column(type: Types::INTEGER, nullable: true)] private ?int $siren = null; - #[ORM\Column(type: Types::SMALLINT, nullable: true)] + #[ORM\Column(type: Types::INTEGER, nullable: true)] private ?int $codeEpci = null; #[ORM\Column(length: 255, nullable: true)] @@ -74,6 +74,18 @@ class Stats #[ORM\OneToMany(targetEntity: StatsHistory::class, mappedBy: 'stats')] private Collection $statsHistories; + #[ORM\Column(nullable: true)] + private ?\DateTime $date_created = null; + + #[ORM\Column(nullable: true)] + private ?\DateTime $date_modified = null; + + #[ORM\Column(nullable: true)] + private ?int $avec_siret = null; + + #[ORM\Column(nullable: true)] + private ?int $avec_name = null; + // calcule le pourcentage de complétion de la zone public function computeCompletionPercent(): ?int { @@ -352,6 +364,54 @@ class Stats return $this; } + + public function getDateCreated(): ?\DateTime + { + return $this->date_created; + } + + public function setDateCreated(?\DateTime $date_created): static + { + $this->date_created = $date_created; + + return $this; + } + + public function getDateModified(): ?\DateTime + { + return $this->date_modified; + } + + public function setDateModified(?\DateTime $date_modified): static + { + $this->date_modified = $date_modified; + + return $this; + } + + public function getAvecSiret(): ?int + { + return $this->avec_siret; + } + + public function setAvecSiret(?int $avec_siret): static + { + $this->avec_siret = $avec_siret; + + return $this; + } + + public function getAvecName(): ?int + { + return $this->avec_name; + } + + public function setAvecName(?int $avec_name): static + { + $this->avec_name = $avec_name; + + return $this; + } } diff --git a/src/Entity/StatsHistory.php b/src/Entity/StatsHistory.php index 688757f..3337cee 100644 --- a/src/Entity/StatsHistory.php +++ b/src/Entity/StatsHistory.php @@ -29,6 +29,24 @@ class StatsHistory #[ORM\ManyToOne(inversedBy: 'statsHistories')] private ?Stats $stats = null; + #[ORM\Column] + private ?\DateTime $date = null; + + #[ORM\Column(nullable: true)] + private ?int $names_count = null; + + #[ORM\Column(nullable: true)] + private ?int $opening_hours_count = null; + + #[ORM\Column(nullable: true)] + private ?int $website_count = null; + + #[ORM\Column(nullable: true)] + private ?int $address_count = null; + + #[ORM\Column(nullable: true)] + private ?int $siret_count = null; + public function getId(): ?int { @@ -95,4 +113,76 @@ class StatsHistory return $this; } + public function getDate(): ?\DateTime + { + return $this->date; + } + + public function setDate(\DateTime $date): static + { + $this->date = $date; + + return $this; + } + + public function getNamesCount(): ?int + { + return $this->names_count; + } + + public function setNamesCount(?int $names_count): static + { + $this->names_count = $names_count; + + return $this; + } + + public function getOpeningHoursCount(): ?int + { + return $this->opening_hours_count; + } + + public function setOpeningHoursCount(?int $opening_hours_count): static + { + $this->opening_hours_count = $opening_hours_count; + + return $this; + } + + public function getWebsiteCount(): ?int + { + return $this->website_count; + } + + public function setWebsiteCount(?int $website_count): static + { + $this->website_count = $website_count; + + return $this; + } + + public function getAddressCount(): ?int + { + return $this->address_count; + } + + public function setAddressCount(?int $address_count): static + { + $this->address_count = $address_count; + + return $this; + } + + public function getSiretCount(): ?int + { + return $this->siret_count; + } + + public function setSiretCount(?int $siret_count): static + { + $this->siret_count = $siret_count; + + return $this; + } + } diff --git a/templates/admin/stats.html.twig b/templates/admin/stats.html.twig index ca97e46..2cf0edf 100644 --- a/templates/admin/stats.html.twig +++ b/templates/admin/stats.html.twig @@ -24,9 +24,160 @@ {% block javascripts %} {{ parent() }} - - + + + + {% endblock %} {% block body %} @@ -60,7 +211,7 @@
- {{ stats.getAvecNote() }} / {{ stats.places|length }} commerces avec note + {{ stats.getAvecNote() }} / {{ stats.places|length }} lieux avec note
@@ -75,38 +226,38 @@
{{ stats.places | length}} - commerces dans la zone. + lieux dans la zone.
{{ stats.getAvecHoraires() }} - commerces avec horaires. + lieux avec horaires.
{{ stats.getAvecAdresse() }} - commerces avec adresse. + lieux avec adresse.
{{ stats.getAvecSite() }} - commerces avec site web renseigné. + lieux avec site web renseigné.
{{ stats.getAvecAccessibilite() }} - commerces avec accessibilité PMR renseignée. + lieux avec accessibilité PMR renseignée.
{{ stats.getAvecNote() }} - commerces avec note renseignée. + lieux avec note renseignée.
@@ -134,9 +285,10 @@
- + {% include 'admin/stats_history.html.twig' %}
+

Tableau des {{ stats.places |length }} lieux

@@ -179,6 +331,14 @@ Date Places Complétion + Emails count + Emails sent + Opening hours + Address + Website + Siret + {# Accessibilite #} + {# Note #} @@ -187,6 +347,14 @@ {{ stat.date|date('d/m/Y') }} {{ stat.placesCount }} {{ stat.completionPercent }}% + {{ stat.emailsCount }} + {{ stat.emailsSent }} + {{ stat.openingHoursCount }} + {{ stat.addressCount }} + {{ stat.websiteCount }} + {{ stat.siretCount }} + {# {{ stat.accessibiliteCount }} #} + {# {{ stat.noteCount }} #} {% endfor %} @@ -255,7 +423,7 @@ data: { labels: ['0-10%', '10-20%', '20-30%', '30-40%', '40-50%', '50-60%', '60-70%', '70-80%', '80-90%', '90-100%'], datasets: [{ - label: 'Nombre de commerces', + label: 'Nombre de lieux', data: distribution, backgroundColor: 'rgba(0, 128, 0, 0.1)', borderColor: 'rgba(0, 128, 0, 1)', @@ -277,7 +445,7 @@ beginAtZero: true, title: { display: true, - text: 'Nombre de commerces' + text: 'Nombre de lieux' } }, x: { @@ -290,7 +458,7 @@ plugins: { title: { display: true, - text: 'Distribution du taux de complétion des commerces' + text: 'Distribution du taux de complétion des lieux' } } } diff --git a/templates/admin/stats_history.html.twig b/templates/admin/stats_history.html.twig new file mode 100644 index 0000000..ab5508e --- /dev/null +++ b/templates/admin/stats_history.html.twig @@ -0,0 +1,62 @@ +
+
+

Évolution du taux de complétion

+
+
+ +
+
+ + diff --git a/templates/public/dashboard.html.twig b/templates/public/dashboard.html.twig index 4cd243c..67bf609 100644 --- a/templates/public/dashboard.html.twig +++ b/templates/public/dashboard.html.twig @@ -53,9 +53,8 @@ {% block javascripts %} {{ parent() }} - +