diff --git a/assets/utils.js b/assets/utils.js
index 7358a94..8b692b7 100644
--- a/assets/utils.js
+++ b/assets/utils.js
@@ -1,4 +1,3 @@
-
function colorHeadingTable() {
const headers = document.querySelectorAll('th');
@@ -21,7 +20,7 @@ function check_validity(e) {
'input[name="commerce_tag_value__contact:email"]',
'input[name="commerce_tag_value__contact:phone"]',
'input[name="commerce_tag_value__contact:website"]',
- 'commerce_tag_value__contact:mastodon',
+ 'input[name="commerce_tag_value__contact:mastodon"]',
'input[name="commerce_tag_value__address"]',
'input[name="custom_opening_hours"]',
'input[name="commerce_tag_value__contact:street"]',
diff --git a/labourage.sh b/labourage.sh
index cc51777..59dca32 100644
--- a/labourage.sh
+++ b/labourage.sh
@@ -12,6 +12,18 @@ codes_insee=(
"33063" # Bordeaux
"59350" # Lille
"35238" # Rennes
+ "75101" # Paris 1er
+ "75102" # Paris 2e
+ "75103" # Paris 3e
+ "75104" # Paris 4e
+ "75105" # Paris 5e
+ "75106" # Paris 6e
+ "75107" # Paris 7e
+ "75108" # Paris 8e
+ "75109" # Paris 9e
+ "75110" # Paris 10e
+ "75111" # Paris 11e
+ "75112" # Paris 12e
"75113" # Paris 13e
"75114" # Paris 14e
"75115" # Paris 15e
diff --git a/migrations/Version20250617141249.php b/migrations/Version20250617141249.php
new file mode 100644
index 0000000..006f42b
--- /dev/null
+++ b/migrations/Version20250617141249.php
@@ -0,0 +1,35 @@
+addSql(<<<'SQL'
+ ALTER TABLE place ADD habitants 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 place DROP habitants
+ SQL);
+ }
+}
diff --git a/migrations/Version20250617141824.php b/migrations/Version20250617141824.php
new file mode 100644
index 0000000..c6d01f3
--- /dev/null
+++ b/migrations/Version20250617141824.php
@@ -0,0 +1,35 @@
+addSql(<<<'SQL'
+ ALTER TABLE stats ADD population 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 DROP population
+ SQL);
+ }
+}
diff --git a/public/assets/img/josm.png b/public/assets/img/josm.png
new file mode 100644
index 0000000..1764904
Binary files /dev/null and b/public/assets/img/josm.png differ
diff --git a/public/assets/img/logo-osm.png b/public/assets/img/logo-osm.png
new file mode 100644
index 0000000..4388167
Binary files /dev/null and b/public/assets/img/logo-osm.png differ
diff --git a/public/assets/img/osm-id.png b/public/assets/img/osm-id.png
new file mode 100644
index 0000000..2ea1914
Binary files /dev/null and b/public/assets/img/osm-id.png differ
diff --git a/public/js/utils.js b/public/js/utils.js
new file mode 100644
index 0000000..bc42570
--- /dev/null
+++ b/public/js/utils.js
@@ -0,0 +1,166 @@
+// Fonction pour gérer la recherche de villes
+/**
+ * Configure la recherche de ville avec autocomplétion
+ * @param {string} inputId - ID de l'input de recherche
+ * @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) {
+ const searchInput = document.getElementById(inputId);
+ const suggestionList = document.getElementById(suggestionListId);
+
+ if (!searchInput || !suggestionList) return;
+
+ let timeoutId = null;
+
+ searchInput.addEventListener('input', function () {
+ clearTimeout(timeoutId);
+ const query = this.value.trim();
+
+ if (query.length < 2) {
+ clearSuggestions();
+ return;
+ }
+
+ timeoutId = setTimeout(() => performSearch(query), 300);
+ });
+
+ function performSearch(query) {
+ fetch(`https://geo.api.gouv.fr/communes?nom=${encodeURIComponent(query)}&fields=nom,code,codesPostaux&limit=5`)
+ .then(response => response.json())
+ .then(data => {
+ const citySuggestions = data.map(city => ({
+ name: city.nom,
+ postcode: city.codesPostaux[0],
+ insee: city.code
+ }));
+ displaySuggestions(citySuggestions);
+ })
+ .catch(error => {
+ console.error('Erreur lors de la recherche:', error);
+ clearSuggestions();
+ });
+ }
+
+ function displaySuggestions(suggestions) {
+ clearSuggestions();
+ suggestions.forEach(suggestion => {
+ const li = document.createElement('li');
+ li.className = 'list-group-item';
+ li.textContent = `${suggestion.name} (${suggestion.postcode})`;
+ li.addEventListener('click', () => {
+ searchInput.value = suggestion.name;
+ clearSuggestions();
+ if (onSelect) onSelect(suggestion);
+ });
+ suggestionList.appendChild(li);
+ });
+ }
+
+ function clearSuggestions() {
+ suggestionList.innerHTML = '';
+ }
+
+ // Fermer les suggestions en cliquant en dehors
+ document.addEventListener('click', function (e) {
+ if (!searchInput.contains(e.target) && !suggestionList.contains(e.target)) {
+ clearSuggestions();
+ }
+ });
+}
+
+// Fonction pour formater l'URL de labourage
+/**
+ * Génère l'URL de labourage pour un code postal donné
+ * @param {string} zipCode - Le code postal
+ * @returns {string} L'URL de labourage
+ */
+export function getLabourerUrl(zipCode) {
+ return `/admin/labourer/${zipCode}`;
+}
+
+// Fonction pour gérer la soumission du formulaire d'ajout de ville
+export function handleAddCityFormSubmit(event) {
+ event.preventDefault();
+ const form = event.target;
+ const submitButton = form.querySelector('button[type="submit"]');
+ const zipCodeInput = form.querySelector('input[name="zip_code"]');
+ if (!zipCodeInput.value) {
+ return;
+ }
+ // Afficher le spinner
+ submitButton.disabled = true;
+ const originalContent = submitButton.innerHTML;
+ submitButton.innerHTML = ' Labourer...';
+ // Rediriger
+ window.location.href = getLabourerUrl(zipCodeInput.value);
+}
+
+/**
+ * Colore les cellules d'un tableau en fonction des pourcentages
+ * @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') {
+ document.querySelectorAll(selector).forEach(cell => {
+ const percentage = parseInt(cell.textContent);
+ if (!isNaN(percentage)) {
+ const alpha = percentage / 100;
+ cell.style.backgroundColor = `rgba(${color}, ${alpha})`;
+ }
+ });
+}
+
+/**
+ * Colore les cellules d'un tableau avec un gradient relatif à la valeur maximale
+ * @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') {
+ // Récupérer toutes les cellules
+ const cells = document.querySelectorAll(selector);
+
+ // Trouver la valeur maximale
+ let maxValue = 0;
+ cells.forEach(cell => {
+ const value = parseInt(cell.textContent);
+ if (!isNaN(value) && value > maxValue) {
+ maxValue = value;
+ }
+ });
+
+ // Appliquer le gradient relatif à la valeur max
+ cells.forEach(cell => {
+ const value = parseInt(cell.textContent);
+ if (!isNaN(value)) {
+ const alpha = value / maxValue; // Ratio relatif au maximum
+ cell.style.backgroundColor = `rgba(${color}, ${alpha})`;
+ }
+ });
+}
+
+/**
+ * Ajuste dynamiquement la taille du texte des éléments list-group-item selon leur nombre
+ * @param {string} selector - Le sélecteur CSS des éléments à ajuster
+ * @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) {
+ const items = document.querySelectorAll(selector);
+ const count = items.length;
+ let fontSize = maxFont;
+ if (count > 0) {
+ // Plus il y a d'items, plus la taille diminue, mais jamais en dessous de minFont
+ fontSize = Math.max(minFont, maxFont - (count - 5) * 0.05);
+ }
+ items.forEach(item => {
+ item.style.fontSize = fontSize + 'rem';
+ });
+}
+
+function check_validity() {
+ if (!document.getElementById('editLand')) {
+ return;
+ }
+ // ... suite du code ...
+}
\ No newline at end of file
diff --git a/src/Controller/AdminController.php b/src/Controller/AdminController.php
index 6c74ab3..0b3b30f 100644
--- a/src/Controller/AdminController.php
+++ b/src/Controller/AdminController.php
@@ -131,10 +131,8 @@ final class AdminController extends AbstractController
// Récupérer ou créer les stats pour cette zone
$stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $zip_code]);
-
$city = $this->motocultrice->get_city_osm_from_zip_code($zip_code);
if (!$stats) {
-
$stats = new Stats();
$stats->setZone($zip_code)
->setPlacesCount(0)
@@ -144,11 +142,27 @@ final class AdminController extends AbstractController
->setAvecAccessibilite(0)
->setAvecNote(0)
->setCompletionPercent(0);
- $this->entityManager->persist($stats);
- $this->entityManager->flush();
- }
+ $this->entityManager->persist($stats);
+ $this->entityManager->flush();
+ }
$stats->setName($city);
+ // Récupérer la population via l'API
+ $population = null;
+ try {
+ $apiUrl = 'https://geo.api.gouv.fr/communes/' . $zip_code . '?fields=population';
+ $response = file_get_contents($apiUrl);
+ if ($response !== false) {
+ $data = json_decode($response, true);
+ if (isset($data['population'])) {
+ $population = (int)$data['population'];
+ $stats->setPopulation($population);
+ }
+ }
+ } catch (\Exception $e) {
+ // Ne rien faire si l'API échoue
+ }
+
// Récupérer toutes les données
$places = $this->motocultrice->labourer($zip_code);
$processedCount = 0;
diff --git a/src/Controller/PublicController.php b/src/Controller/PublicController.php
index 7dff42e..593c560 100644
--- a/src/Controller/PublicController.php
+++ b/src/Controller/PublicController.php
@@ -102,7 +102,7 @@ class PublicController extends AbstractController
$message = (new Email())
->from('contact@osm-commerce.fr')
->to($destinataire)
- ->subject('Votre lien de modification Ope nStreetMap')
+ ->subject('Votre lien de modification OpenStreetMap')
->text('Bonjour, nous sommes des bénévoles d\'OpenStreetMap France et nous vous proposons de modifier les informations de votre commerce. Voici votre lien unique de modification: ' . $this->generateUrl('app_public_edit', [
'zipcode' => $zipCode,
'name' => $place_name,
@@ -117,10 +117,11 @@ class PublicController extends AbstractController
#[Route('/', name: 'app_public_index')]
public function index(): Response
{
+ $stats = $this->entityManager->getRepository(Stats::class)->findAll();
return $this->render('public/home.html.twig', [
'controller_name' => 'PublicController',
-
+ 'stats' => $stats
]);
}
@@ -190,7 +191,6 @@ class PublicController extends AbstractController
#[Route('/modify/{osm_object_id}/{version}/{changesetID}', name: 'app_public_submit')]
public function submit($osm_object_id, $version, $changesetID): Response
{
-
$place = $this->entityManager->getRepository(Place::class)->findOneBy(['osmId' => $osm_object_id]);
if (!$place) {
$this->addFlash('warning', 'Ce commerce n\'existe pas.');
@@ -212,11 +212,9 @@ class PublicController extends AbstractController
$request_post = $request->request->all();
-
$request_post = $this->motocultrice->map_post_values($request_post);
foreach ($request_post as $key => $value) {
-
if (strpos($key, 'commerce_tag_value__') === 0) {
$tagKey = str_replace('commerce_tag_value__', '', $key);
if (!empty($value)) {
@@ -236,7 +234,6 @@ class PublicController extends AbstractController
// Récupérer le token OSM depuis les variables d'environnement
$osm_api_token = $_ENV['APP_OSM_BEARER'];
-
$exception = false;
$exception_message = "";
try {
@@ -293,7 +290,6 @@ class PublicController extends AbstractController
// Debug du XML généré
$xmlString = $xml->asXML();
- // echo('xml :
'.$xmlString);
$response = $client->put("https://api.openstreetmap.org/api/0.6/{$osm_kind}/" . $osm_object_id, [
'body' => $xmlString,
@@ -327,15 +323,13 @@ class PublicController extends AbstractController
}
// après envoi on récupère les données
- $commerce = $this->motocultrice->get_osm_object_data($osm_kind, $osm_object_id);
+ $commerce_overpass = $this->motocultrice->get_osm_object_data($osm_kind, $osm_object_id);
-
- $place->update_place_from_overpass_data($commerce);
+ $place->update_place_from_overpass_data($commerce_overpass);
$this->entityManager->persist($place);
$this->entityManager->flush();
$this->entityManager->clear();
-
$stats = $place->getStats();
if(!$stats) {
$stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zip_code' => $place->getZipCode()]);
@@ -345,19 +339,20 @@ class PublicController extends AbstractController
$stats->setZipCode($place->getZipCode());
}
- $stats->addPlace($place);
- $place->setStats($stats);
- $place->setModifiedDate(new \DateTime());
+ $stats->addPlace($place);
+ $place->setStats($stats);
+ $place->setModifiedDate(new \DateTime());
- $stats->computeCompletionPercent();
- $this->entityManager->persist($stats);
- $this->entityManager->persist($place);
- $this->entityManager->flush();
- $this->entityManager->clear();
+ $stats->computeCompletionPercent();
+ $this->entityManager->persist($stats);
+ $this->entityManager->persist($place);
+ $this->entityManager->flush();
+ $this->entityManager->clear();
return $this->render('public/view.html.twig', [
'controller_name' => 'PublicController',
- 'commerce' => $commerce,
+ 'commerce' => $commerce_overpass,
+ 'commerce_overpass' => $commerce_overpass,
'place' => $place,
'status' => $status,
'exception' => $exception,
diff --git a/src/Entity/Place.php b/src/Entity/Place.php
index 1ceb91b..a3d3328 100644
--- a/src/Entity/Place.php
+++ b/src/Entity/Place.php
@@ -100,6 +100,9 @@ class Place
#[ORM\Column(length: 255, nullable: true)]
private ?string $siret = null;
+ #[ORM\Column(nullable: true)]
+ private ?int $habitants = null;
+
public function getMainTag(): ?string
{
return $this->main_tag;
@@ -579,4 +582,16 @@ class Place
return $this;
}
+
+ public function getHabitants(): ?int
+ {
+ return $this->habitants;
+ }
+
+ public function setHabitants(?int $habitants): static
+ {
+ $this->habitants = $habitants;
+
+ return $this;
+ }
}
diff --git a/src/Entity/Stats.php b/src/Entity/Stats.php
index 872fda9..6db0de4 100644
--- a/src/Entity/Stats.php
+++ b/src/Entity/Stats.php
@@ -17,7 +17,7 @@ class Stats
private ?int $id = null;
#[ORM\Column(length: 255)]
- private ?string $zone = null;
+ private ?string $zone = null; // code insee de la zone
#[ORM\Column(type: Types::SMALLINT)]
private ?int $completion_percent = null;
@@ -55,6 +55,10 @@ class Stats
#[ORM\Column(length: 255, nullable: true)]
private ?string $name = null;
+ // nombre d'habitants dans la zone
+ #[ORM\Column(type: Types::INTEGER, nullable: true)]
+ private ?int $population = null;
+
// calcule le pourcentage de complétion de la zone
public function computeCompletionPercent(): ?int
{
@@ -255,6 +259,17 @@ class Stats
return $this;
}
+
+ public function getPopulation(): ?int
+ {
+ return $this->population;
+ }
+
+ public function setPopulation(?int $population): static
+ {
+ $this->population = $population;
+ return $this;
+ }
}
diff --git a/templates/admin/stats.html.twig b/templates/admin/stats.html.twig
index 933415a..a4cf711 100644
--- a/templates/admin/stats.html.twig
+++ b/templates/admin/stats.html.twig
@@ -16,6 +16,9 @@
height: 300px;
margin: 20px 0;
}
+ .completion-info {
+ margin-bottom: 2rem;
+ }
{% endblock %}
@@ -34,6 +37,27 @@
+ {% if stats.population %}
+
- {{query_places|raw}} --
+ {{query_places|raw}} ++ + Exécuter dans Overpass Turbo + +
+ | {{ commerce.getCompletionPercentage() }} |
diff --git a/templates/public/dashboard.html.twig b/templates/public/dashboard.html.twig
index a317a01..b1a21ce 100644
--- a/templates/public/dashboard.html.twig
+++ b/templates/public/dashboard.html.twig
@@ -14,216 +14,184 @@
width: 100%;
margin-bottom: 1rem;
}
+ .suggestion-list {
+ position: absolute;
+ background: white;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ max-height: 200px;
+ overflow-y: auto;
+ width: 100%;
+ z-index: 1000;
+ display: none;
+ }
+ .suggestion-item {
+ padding: 8px 12px;
+ cursor: pointer;
+ border-bottom: 1px solid #eee;
+ }
+ .suggestion-item:hover {
+ background-color: #f5f5f5;
+ }
+ .suggestion-name {
+ font-weight: bold;
+ }
+ .suggestion-details {
+ font-size: 0.9em;
+ color: #666;
+ }
+ .suggestion-type {
+ margin-right: 8px;
+ }
+ .search-container {
+ position: relative;
+ margin-bottom: 1rem;
+ }
{% endblock %}
{% block javascripts %}
{{ parent() }}
-
-
-
+
{% endblock %}
{% block body %}
-
+
-
+ Dashboard+Tableau de bord
-
+ Statistiques : {{ stats|length }} codes postaux- - +Statistiques par ville+
+
+
{{ places_count }} Lieux- -
-
- - {{ dump(commerce_overpass) }} -+
@@ -19,7 +15,7 @@
{% if commerce_overpass is not empty %}
-
@@ -130,23 +123,31 @@
{{ 'display.by'|trans }}
{{ commerce_overpass['@attributes'].user }}
-
-
+ {% if commerce.stats %}
+
+ zone {{commerce.stats.zone}}
+
+ {% endif %}
+
-
- Dashboard
+ Dashboard
diff --git a/templates/public/edit/tags.html.twig b/templates/public/edit/tags.html.twig
index ee403ef..35500ac 100644
--- a/templates/public/edit/tags.html.twig
+++ b/templates/public/edit/tags.html.twig
@@ -1,43 +1,45 @@
{% block tags %}
|