From 1659864efbff5d31bfb8d6114b93147a85f485ed Mon Sep 17 00:00:00 2001 From: Tykayn Date: Tue, 12 Aug 2025 11:58:11 +0200 Subject: [PATCH] add measure on checking advanced graph --- src/Controller/FollowUpController.php | 64 +++++++------ src/Repository/CityFollowUpRepository.php | 5 +- .../admin/followup_theme_graph.html.twig | 96 ++++++++++++++++++- 3 files changed, 133 insertions(+), 32 deletions(-) diff --git a/src/Controller/FollowUpController.php b/src/Controller/FollowUpController.php index f2da51a2..0c24ba4c 100644 --- a/src/Controller/FollowUpController.php +++ b/src/Controller/FollowUpController.php @@ -2,13 +2,12 @@ namespace App\Controller; -use App\Entity\Stats; use App\Entity\CityFollowUp; -use App\Service\Motocultrice; +use App\Entity\Stats; use App\Service\FollowUpService; +use App\Service\Motocultrice; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; @@ -23,12 +22,13 @@ class FollowUpController extends AbstractController #[Route('/api/city-followup', name: 'api_city_followup', methods: ['POST'])] public function recordCityFollowUp( - EntityManagerInterface $em, + EntityManagerInterface $em, \Symfony\Component\HttpFoundation\Request $request - ): Response { + ): Response + { $insee_code = $request->request->get('insee_code'); $measure_label = $request->request->get('measure_label'); - $measure_value = $request->request->getFloat('measure_value'); + $measure_value = (float)$request->request->get('measure_value'); if (!$insee_code || !$measure_label || $measure_value === null) { return $this->json([ @@ -48,9 +48,9 @@ class FollowUpController extends AbstractController // Check if the same measure was recorded less than an hour ago $oneHourAgo = new \DateTime('-1 hour'); $recentFollowUp = $em->getRepository(CityFollowUp::class) - ->findRecentByStatsAndName($stats, $measure_label, $oneHourAgo); + ->findRecentByStatsAndName($stats->getId(), $measure_label, $oneHourAgo); - if ($recentFollowUp) { + if ($recentFollowUp && $recentFollowUp->getMeasure() == $measure_value) { return $this->json([ 'success' => false, 'message' => 'A measure with the same label was recorded less than an hour ago', @@ -86,7 +86,8 @@ class FollowUpController extends AbstractController #[Route('/admin/followup/{insee_code}/delete', name: 'admin_followup_delete', requirements: ['insee_code' => '\\d+'])] - public function deleteFollowups(string $insee_code, EntityManagerInterface $em): Response { + public function deleteFollowups(string $insee_code, EntityManagerInterface $em): Response + { $stats = $em->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]); if (!$stats) { $this->addFlash('error', '9 Aucune stats trouvée pour ce code INSEE.'); @@ -103,10 +104,11 @@ class FollowUpController extends AbstractController #[Route('/admin/followup/{insee_code}', name: 'admin_followup', requirements: ['insee_code' => '\\d+'])] public function followup( - string $insee_code, - Motocultrice $motocultrice, + string $insee_code, + Motocultrice $motocultrice, EntityManagerInterface $em - ): Response { + ): Response + { $stats = $em->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]); if (!$stats) { $this->addFlash('error', '10 Aucune stats trouvée pour ce code INSEE.'); @@ -119,10 +121,11 @@ class FollowUpController extends AbstractController #[Route('/admin/followup/{insee_code}/graph', name: 'admin_followup_graph', requirements: ['insee_code' => '\\d+'])] public function followupGraph( - string $insee_code, + string $insee_code, EntityManagerInterface $em, - Motocultrice $motocultrice - ) { + Motocultrice $motocultrice + ) + { $stats = $em->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]); if (!$stats) { $this->addFlash('error', '11 Aucune stats trouvée pour ce code INSEE.'); @@ -168,7 +171,7 @@ class FollowUpController extends AbstractController $all_points = array_slice($all_points, 0, 20); // Tri par date dans chaque série foreach ($series as &$points) { - usort($points, function($a, $b) { + usort($points, function ($a, $b) { return strtotime($a['date']) <=> strtotime($b['date']); }); } @@ -189,7 +192,7 @@ class FollowUpController extends AbstractController $count_series = $series[$type . '_count'] ?? []; $completion_series = $series[$type . '_completion'] ?? []; // Fonction utilitaire pour trouver la valeur la plus proche avant ou égale à une date - $findValueAtOrBefore = function($series, \DateTime $date) { + $findValueAtOrBefore = function ($series, \DateTime $date) { $val = null; foreach (array_reverse($series) as $point) { $ptDate = \DateTime::createFromFormat('Y-m-d', $point['date']); @@ -201,7 +204,7 @@ class FollowUpController extends AbstractController return $val; }; // Valeurs aux bornes - $val_now = count($count_series) ? $count_series[count($count_series)-1]['value'] : null; + $val_now = count($count_series) ? $count_series[count($count_series) - 1]['value'] : null; $val_7j = $findValueAtOrBefore($count_series, $periods['7j']); $val_30j = $findValueAtOrBefore($count_series, $periods['30j']); $val_6mois = $findValueAtOrBefore($count_series, $periods['6mois']); @@ -210,7 +213,7 @@ class FollowUpController extends AbstractController $diff_30j = ($val_7j !== null && $val_30j !== null) ? $val_7j - $val_30j : null; $diff_6mois = ($val_30j !== null && $val_6mois !== null) ? $val_30j - $val_6mois : null; // Idem pour la complétion - $comp_now = count($completion_series) ? $completion_series[count($completion_series)-1]['value'] : null; + $comp_now = count($completion_series) ? $completion_series[count($completion_series) - 1]['value'] : null; $comp_7j = $findValueAtOrBefore($completion_series, $periods['7j']); $comp_30j = $findValueAtOrBefore($completion_series, $periods['30j']); $comp_6mois = $findValueAtOrBefore($completion_series, $periods['6mois']); @@ -251,8 +254,9 @@ class FollowUpController extends AbstractController #[Route('/admin/followup/all', name: 'admin_followup_all')] public function followupAll( EntityManagerInterface $em, - Motocultrice $motocultrice - ) { + Motocultrice $motocultrice + ) + { $statsList = $em->getRepository(Stats::class)->findAll(); foreach ($statsList as $stats) { $this->followUpService->generateCityFollowUps($stats, $motocultrice, $em); @@ -262,14 +266,16 @@ class FollowUpController extends AbstractController } #[Route('/admin/followup/global', name: 'admin_followup_global')] - public function followupGlobal(EntityManagerInterface $em) { + public function followupGlobal(EntityManagerInterface $em) + { $this->followUpService->generateGlobalFollowUps($em); $this->addFlash('success', 'Suivi global généré pour toutes les villes.'); return $this->redirectToRoute('admin_followup_global_graph'); } #[Route('/admin/followup/global/graph', name: 'admin_followup_global_graph')] - public function followupGlobalGraph(EntityManagerInterface $em) { + public function followupGlobalGraph(EntityManagerInterface $em) + { $stats = $em->getRepository(Stats::class)->findOneBy(['zone' => '00000']); if (!$stats) { $this->addFlash('error', 'Aucun suivi global trouvé.'); @@ -288,7 +294,7 @@ class FollowUpController extends AbstractController } // Tri par date dans chaque série foreach ($series as &$points) { - usort($points, function($a, $b) { + usort($points, function ($a, $b) { return strtotime($a['date']) <=> strtotime($b['date']); }); } @@ -304,11 +310,12 @@ class FollowUpController extends AbstractController #[Route('/admin/followup/{insee_code}/embed/{theme}', name: 'admin_followup_embed_graph', requirements: ['insee_code' => '\\d+', 'theme' => '[a-zA-Z0-9_]+'])] public function followupEmbedGraph( - string $insee_code, - string $theme, + string $insee_code, + string $theme, EntityManagerInterface $em, - Motocultrice $motocultrice - ) { + Motocultrice $motocultrice + ) + { $stats = $em->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]); if (!$stats) { $this->addFlash('error', '12 Aucune stats trouvée pour ce code INSEE.'); @@ -353,6 +360,7 @@ class FollowUpController extends AbstractController 'icon' => FollowUpService::getFollowUpIcons()[$theme] ?? 'bi-question-circle', ]); } + #[Route('/admin/followup/unused-stores', name: 'admin_followup_unused_stores')] public function unusedStores(): Response { diff --git a/src/Repository/CityFollowUpRepository.php b/src/Repository/CityFollowUpRepository.php index e2e1b71a..f50ceeea 100644 --- a/src/Repository/CityFollowUpRepository.php +++ b/src/Repository/CityFollowUpRepository.php @@ -17,7 +17,7 @@ class CityFollowUpRepository extends ServiceEntityRepository } - public function findRecentByStatsAndName(Stats $stats, string $name, \DateTime $since): ?CityFollowUp + public function findRecentByStatsAndName(string $stats, string $name, \DateTime $since): ?CityFollowUp { return $this->createQueryBuilder('c') ->andWhere('c.stats = :stats') @@ -29,7 +29,6 @@ class CityFollowUpRepository extends ServiceEntityRepository ->orderBy('c.date', 'DESC') ->setMaxResults(1) ->getQuery() - ->getOneOrNullResult() - ; + ->getOneOrNullResult(); } } diff --git a/templates/admin/followup_theme_graph.html.twig b/templates/admin/followup_theme_graph.html.twig index 42b9d05f..b9af1781 100644 --- a/templates/admin/followup_theme_graph.html.twig +++ b/templates/admin/followup_theme_graph.html.twig @@ -527,6 +527,77 @@ if (average_completion && current_completion) { current_completion.textContent = average_completion.toFixed(2) + ' %'; } + + // Send measurement to /api/city-followup + const insee_code = '{{ stats.zone }}'; + const theme = '{{ theme }}'; + + // Prepare data for the API request + const measureData = new FormData(); + measureData.append('insee_code', insee_code); + measureData.append('measure_label', theme + '_count'); + measureData.append('measure_value', count_objects); + + // Send count measurement + fetch('/api/city-followup', { + method: 'POST', + body: measureData + }) + .then(response => response.json()) + .then(result => { + console.log('Count measurement saved:', result); + if (result.success) { + // Add the new measurement to the chart data + const newMeasurement = { + date: result.follow_up.date, + value: result.follow_up.measure + }; + + // Add to the global countData array + if (Array.isArray(window.countData)) { + window.countData.push(newMeasurement); + // Update the chart + updateChart(); + } + } + }) + .catch(error => { + console.error('Error saving count measurement:', error); + }); + + // Send completion measurement + if (average_completion) { + const completionData = new FormData(); + completionData.append('insee_code', insee_code); + completionData.append('measure_label', theme + '_completion'); + completionData.append('measure_value', average_completion); + + fetch('/api/city-followup', { + method: 'POST', + body: completionData + }) + .then(response => response.json()) + .then(result => { + console.log('Completion measurement saved:', result); + if (result.success) { + // Add the new measurement to the chart data + const newMeasurement = { + date: result.follow_up.date, + value: result.follow_up.measure + }; + + // Add to the global completionData array + if (Array.isArray(window.completionData)) { + window.completionData.push(newMeasurement); + // Update the chart + updateChart(); + } + } + }) + .catch(error => { + console.error('Error saving completion measurement:', error); + }); + } const tbody = document.querySelector('#tags-stats-table tbody'); if (Object.keys(tagCounts).length === 0) { tbody.innerHTML = 'Aucun tag trouvé'; @@ -551,8 +622,9 @@ const countData = {{ count_data|json_encode|raw }}; console.log('Count data:', countData); - window.countData = countData + window.countData = countData; const completionData = {{ completion_data|json_encode|raw }}; + window.completionData = completionData; console.log('Completion data:', completionData); // Current metrics from server @@ -694,6 +766,28 @@ } }); + // Function to update the chart with new data + function updateChart() { + // Get the chart instance + const chartInstance = Chart.getChart('themeChart'); + if (!chartInstance) return; + + // Update the datasets + chartInstance.data.datasets[0].data = Array.isArray(window.countData) + ? window.countData.map(d => ({x: new Date(d.date), y: d.value})) + : []; + + if (Array.isArray(window.completionData)) { + chartInstance.data.datasets[1].data = window.completionData.map(d => ({ + x: new Date(d.date), + y: d.value + })); + } + + // Update the chart + chartInstance.update(); + } + // Initialiser les statistiques updateStats();