diff --git a/assets/app.js b/assets/app.js
index 5e941e38..351dbe6e 100644
--- a/assets/app.js
+++ b/assets/app.js
@@ -36,9 +36,8 @@ import {
toggleCompletionInfo,
updateMapHeightForLargeScreens
} from './utils.js';
-// import Tablesort from 'tablesort';
-import TableSort from 'table-sort-js/table-sort.js';
-console.log('TableSort', TableSort)
+import tableSortJs from 'table-sort-js/table-sort.js';
+console.log('TableSort', tableSortJs)
// Charger table-sortable (version non minifiée locale)
// import '../assets/js/table-sortable.js';
@@ -203,11 +202,10 @@ document.addEventListener('DOMContentLoaded', () => {
adjustListGroupFontSize('.list-group-item');
// Activer le tri naturel sur tous les tableaux avec la classe table-sort
- if(TableSort){
-
- document.querySelectorAll('table.table-sort')?.forEach(table => {
- new TableSort(table);
- });
+ if (tableSortJs) {
+ tableSortJs();
+ }else{
+ console.log('pas de tablesort')
}
// Initialisation du tri et filtrage sur les tableaux du dashboard et de la page stats
diff --git a/assets/styles/app.css b/assets/styles/app.css
index d4ac0bc5..f37a0bd3 100644
--- a/assets/styles/app.css
+++ b/assets/styles/app.css
@@ -31,6 +31,11 @@ body {
color: #df5a0d;
}
+table {
+ max-height: 100vh;
+ max-width: 100vw;
+}
+
table.js-sort-table th {
cursor: pointer;
}
@@ -141,7 +146,7 @@ img {
}
-#completionHistoryChart{
+#completionHistoryChart {
min-height: 500px;
}
diff --git a/migrations/Version20250626204942.php b/migrations/Version20250626204942.php
new file mode 100644
index 00000000..5344d855
--- /dev/null
+++ b/migrations/Version20250626204942.php
@@ -0,0 +1,35 @@
+addSql(<<<'SQL'
+ CREATE TABLE action_log (id INT AUTO_INCREMENT NOT NULL, kind VARCHAR(255) NOT NULL, from_url VARCHAR(500) NOT NULL, who VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8
+ SQL);
+ }
+
+ public function down(Schema $schema): void
+ {
+ // this down() migration is auto-generated, please modify it to your needs
+ $this->addSql(<<<'SQL'
+ DROP TABLE action_log
+ SQL);
+ }
+}
diff --git a/migrations/Version20250626205820.php b/migrations/Version20250626205820.php
new file mode 100644
index 00000000..512b9ea6
--- /dev/null
+++ b/migrations/Version20250626205820.php
@@ -0,0 +1,35 @@
+addSql(<<<'SQL'
+ ALTER TABLE action_log ADD from_url VARCHAR(500) 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 action_log DROP from_url
+ SQL);
+ }
+}
diff --git a/migrations/Version20250626210012.php b/migrations/Version20250626210012.php
new file mode 100644
index 00000000..172cebb1
--- /dev/null
+++ b/migrations/Version20250626210012.php
@@ -0,0 +1,35 @@
+addSql(<<<'SQL'
+ ALTER TABLE action_log ADD who VARCHAR(255) 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 action_log DROP who
+ SQL);
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index 953c42e8..e605a0cd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6,7 +6,8 @@
"": {
"license": "UNLICENSED",
"dependencies": {
- "jquery": "^3.7.1"
+ "jquery": "^3.7.1",
+ "table-sort": "^1.0.16"
},
"devDependencies": {
"@babel/core": "^7.17.0",
@@ -5501,6 +5502,12 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
+ "node_modules/table-sort": {
+ "version": "1.0.16",
+ "resolved": "https://registry.npmjs.org/table-sort/-/table-sort-1.0.16.tgz",
+ "integrity": "sha512-w7TDMfszdFY36aWQKRiAg0qQjOmvIy1IQKplmgpOCimOZ69BP4y5Ne4+jBQeYn990Rn40/wCALR0eAcLqxECWA==",
+ "license": "ISC"
+ },
"node_modules/table-sort-js": {
"version": "1.22.2",
"resolved": "https://registry.npmjs.org/table-sort-js/-/table-sort-js-1.22.2.tgz",
diff --git a/package.json b/package.json
index 8e5215b2..0d62501b 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
"build": "encore production --progress"
},
"dependencies": {
- "jquery": "^3.7.1"
+ "jquery": "^3.7.1",
+ "table-sort": "^1.0.16"
}
}
diff --git a/src/Controller/AdminController.php b/src/Controller/AdminController.php
index 4015981d..ce4152df 100644
--- a/src/Controller/AdminController.php
+++ b/src/Controller/AdminController.php
@@ -17,6 +17,7 @@ use function uuid_create;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpFoundation\JsonResponse;
use Twig\Environment;
+use App\Service\ActionLogger;
final class AdminController extends AbstractController
{
@@ -25,7 +26,8 @@ final class AdminController extends AbstractController
private EntityManagerInterface $entityManager,
private Motocultrice $motocultrice,
private BudgetService $budgetService,
- private Environment $twig
+ private Environment $twig,
+ private ActionLogger $actionLogger
) {
}
@@ -145,6 +147,7 @@ final class AdminController extends AbstractController
$stats->setAvecSite($calculatedStats['counters']['avec_site']);
$stats->setAvecAccessibilite($calculatedStats['counters']['avec_accessibilite']);
$stats->setAvecNote($calculatedStats['counters']['avec_note']);
+
$stats->setCompletionPercent($calculatedStats['completion_percent']);
// Associer les stats à chaque commerce
@@ -730,15 +733,9 @@ final class AdminController extends AbstractController
// Afficher le log des objets non trouvés à la fin
if (!empty($notFoundOsmKeys)) {
- return $this->render('admin/labourage_results.html.twig', [
- 'stats' => $stats,
- 'zone' => $insee_code,
- 'new_places_counter' => $processedCount,
- 'commerces' => $commerces,
- 'not_found_osm_keys' => $notFoundOsmKeys
- ]);
+ $this->addFlash('info', count($notFoundOsmKeys).' objets OSM non trouvés lors du labourage.');
}
- // Sinon, rediriger comme avant
+ // Rediriger dans tous les cas vers la page de stats de la ville
return $this->redirectToRoute('app_admin_stats', ['insee_code' => $insee_code]);
} catch (\Exception $e) {
$this->addFlash('error', 'Erreur lors du labourage : ' . $e->getMessage());
diff --git a/src/Controller/PublicController.php b/src/Controller/PublicController.php
index 0d7474d3..5c887ea0 100644
--- a/src/Controller/PublicController.php
+++ b/src/Controller/PublicController.php
@@ -13,6 +13,7 @@ use GuzzleHttp\Client;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Mime\Email;
use Symfony\Component\Mailer\MailerInterface;
+use App\Service\ActionLogger;
class PublicController extends AbstractController
{
@@ -22,19 +23,19 @@ class PublicController extends AbstractController
public function __construct(
private EntityManagerInterface $entityManager,
private Motocultrice $motocultrice,
- private MailerInterface $mailer
- ) {
- }
+ private MailerInterface $mailer,
+ private ActionLogger $actionLogger
+ ) {}
#[Route('/propose-email/{email}/{type}/{id}', name: 'app_public_propose_email')]
public function proposeEmail(string $email, string $type, int $id): Response
{
- $data = $this->motocultrice->get_osm_object_data($type, $id);
- // Récupérer le code postal depuis les tags, sinon mettre -1
- $zipCode = isset($data['tags_converted']['addr:postcode']) ? (int)$data['tags_converted']['addr:postcode'] : -1;
- $place_name = $data['tags_converted']['name'];
+ $data = $this->motocultrice->get_osm_object_data($type, $id);
+ // Récupérer le code postal depuis les tags, sinon mettre -1
+ $zipCode = isset($data['tags_converted']['addr:postcode']) ? (int)$data['tags_converted']['addr:postcode'] : -1;
+ $place_name = $data['tags_converted']['name'];
// Vérifier si une Place existe déjà avec le même osm_kind et osmId
$existingPlace = $this->entityManager->getRepository(Place::class)->findOneBy([
@@ -45,10 +46,10 @@ class PublicController extends AbstractController
if ($existingPlace) {
// Mettre à jour l'email de la Place existante
$existingPlace->setEmail($email)->setLastContactAttemptDate(new \DateTime());
- if($zipCode != -1) {
+ if ($zipCode != -1) {
$existingPlace->setZipCode($zipCode);
}
- $this->entityManager->persist($existingPlace);
+ $this->entityManager->persist($existingPlace);
$this->entityManager->flush();
$debug = '';
@@ -59,28 +60,28 @@ class PublicController extends AbstractController
'uuid' => $existingPlace->getUuidForUrl()
], true);
}
-
- $this->addFlash('success', 'L\'email a été mis à jour. Un email vous sera envoyé avec le lien de modification. '.$debug);
+
+ $this->addFlash('success', 'L\'email a été mis à jour. Un email vous sera envoyé avec le lien de modification. ' . $debug);
} else {
// Créer une nouvelle entité Place
$place = new Place();
$place->setEmail($email)
- ->setOsmId($id)
- ->setOsmKind($type)
- ->setAskedHumainsSupport(false)
- ->setOptedOut(false)
- ->setDead(false)
- ->setNote('')
- ->setModifiedDate(new \DateTime())
- ->setZipCode($zipCode)
- ->setPlaceCount(0)
- ->setMainTag($this->motocultrice->find_main_tag($data['tags_converted']) ?? '')
- ->setStreet($this->motocultrice->find_street($data['tags_converted']) ?? '')
- ->setHousenumber($this->motocultrice->find_housenumber($data['tags_converted']) ?? '')
- ->setLastContactAttemptDate(new \DateTime())
- ->setUuidForUrl(uniqid());
+ ->setOsmId($id)
+ ->setOsmKind($type)
+ ->setAskedHumainsSupport(false)
+ ->setOptedOut(false)
+ ->setDead(false)
+ ->setNote('')
+ ->setModifiedDate(new \DateTime())
+ ->setZipCode($zipCode)
+ ->setPlaceCount(0)
+ ->setMainTag($this->motocultrice->find_main_tag($data['tags_converted']) ?? '')
+ ->setStreet($this->motocultrice->find_street($data['tags_converted']) ?? '')
+ ->setHousenumber($this->motocultrice->find_housenumber($data['tags_converted']) ?? '')
+ ->setLastContactAttemptDate(new \DateTime())
+ ->setUuidForUrl(uniqid());
$this->entityManager->persist($place);
$this->entityManager->flush();
@@ -93,7 +94,7 @@ class PublicController extends AbstractController
'uuid' => $place->getUuidForUrl()
], true);
}
- $this->addFlash('success', 'Un email vous sera envoyé avec le lien de modification. '.$debug);
+ $this->addFlash('success', 'Un email vous sera envoyé avec le lien de modification. ' . $debug);
}
// Envoyer l'email
@@ -118,7 +119,7 @@ class PublicController extends AbstractController
public function index(): Response
{
$stats = $this->entityManager->getRepository(Stats::class)->findAll();
-
+
return $this->render('public/home.html.twig', [
'controller_name' => 'PublicController',
'stats' => $stats
@@ -128,9 +129,15 @@ class PublicController extends AbstractController
#[Route('/edit/{zipcode}/{name}/{uuid}', name: 'app_public_edit')]
public function edit_with_uuid($zipcode, $name, $uuid): Response
{
+ $this->actionLogger->log('dashboard', [
+ 'zipcode' => $zipcode,
+
+
+ ]);
+
$place = $this->entityManager->getRepository(Place::class)->findOneBy(['uuid_for_url' => $uuid]);
if (!$place) {
- $this->addFlash('warning', 'Ce lien de modification n\'existe pas.'.$uuid);
+ $this->addFlash('warning', 'Ce lien de modification n\'existe pas.' . $uuid);
return $this->redirectToRoute('app_public_index');
}
@@ -142,19 +149,19 @@ class PublicController extends AbstractController
// récupérer les tags de base
$base_tags = $this->motocultrice->base_tags;
$base_tags = array_fill_keys($base_tags, '');
-
+
$commerce_overpass = $this->motocultrice->get_osm_object_data($place->getOsmKind(), $place->getOsmId());
// Fusionner les tags de base avec les tags existants
-
- $commerce_overpass['tags_converted'] = array_merge($base_tags, $commerce_overpass['tags_converted']);
-
+
+ $commerce_overpass['tags_converted'] = array_merge($base_tags, $commerce_overpass['tags_converted']);
+
// Trier les tags par ordre alphabétique des clés
ksort($commerce_overpass['tags_converted']);
$place->setDisplayedDate(new \DateTime());
$this->entityManager->persist($place);
$this->entityManager->flush();
-
+
return $this->render('public/edit.html.twig', [
'commerce_overpass' => $commerce_overpass,
'name' => $name,
@@ -172,7 +179,9 @@ class PublicController extends AbstractController
#[Route('/dashboard', name: 'app_public_dashboard')]
public function dashboard(): Response
{
-
+
+ $this->actionLogger->log('dashboard', []);
+
$stats_repo = $this->entityManager->getRepository(Stats::class)->findAll();
$stats_for_chart = [];
@@ -204,6 +213,12 @@ class PublicController extends AbstractController
#[Route('/modify/{osm_object_id}/{version}/{changesetID}', name: 'app_public_submit')]
public function submit($osm_object_id, $version, $changesetID): Response
{
+
+ $this->actionLogger->log('submit_object', [
+ 'osm_id' => $osm_object_id,
+ 'version' => $version,
+ 'changesetID' => $changesetID
+ ]);
$place = $this->entityManager->getRepository(Place::class)->findOneBy(['osmId' => $osm_object_id]);
if (!$place) {
$this->addFlash('warning', 'Ce commerce n\'existe pas.');
@@ -212,19 +227,19 @@ class PublicController extends AbstractController
// Récupérer les données POST
$request = Request::createFromGlobals();
-
+
// Vérifier si des données ont été soumises
if ($request->isMethod('POST')) {
$status = "non modifié";
-
+
// Récupérer le type d'objet (node ou way)
$osm_kind = $request->request->get('osm_kind', 'node');
-
+
// Récupérer tous les tags du formulaire
$tags = [];
$request_post = $request->request->all();
-
+
$request_post = $this->motocultrice->map_post_values($request_post);
foreach ($request_post as $key => $value) {
@@ -258,7 +273,7 @@ class PublicController extends AbstractController
$tag = $changeset->addChild('tag');
$tag->addAttribute('k', 'created_by');
$tag->addAttribute('v', 'OSM Mon Commerce Web Editor');
-
+
$tag = $changeset->addChild('tag');
$tag->addAttribute('k', 'comment');
$tag->addAttribute('v', 'Modification dans #MonCommerceOSM');
@@ -275,14 +290,14 @@ class PublicController extends AbstractController
// Récupérer les données actuelles de l'objet
$currentObjectData = $this->motocultrice->get_osm_object_data($osm_kind, $osm_object_id);
-
+
// 2. Modifier l'objet avec le nouveau changeset
$xml = new \SimpleXMLElement('