diff --git a/assets/utils.js b/assets/utils.js index 8b692b7..140e648 100644 --- a/assets/utils.js +++ b/assets/utils.js @@ -179,6 +179,291 @@ function openInPanoramax() { window.open(panoramaxUrl); } + +function enableLabourageForm() { + + // Récupérer les éléments du formulaire + const citySearchInput = document.getElementById('citySearch'); + const citySuggestionsList = document.getElementById('citySuggestions'); + + if (citySearchInput && citySuggestionsList) { + // Configurer la recherche de ville avec la fonction existante + setupCitySearch('citySearch', 'citySuggestions', function (result_search) { + console.log('code_insee', result_search.insee); + // Activer le spinner dans le bouton de labourage + const labourageBtn = document.querySelector('.btn-labourer'); + if (labourageBtn) { + labourageBtn.innerHTML = ' Chargement...'; + labourageBtn.disabled = true; + } + console.log('result_search', result_search, getLabourerUrl(result_search)); + window.location.href = getLabourerUrl(result_search); + }); + } +} +// 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 + */ +function setupCitySearch(inputId, suggestionListId, onSelect) { + const searchInput = document.getElementById(inputId); + const suggestionList = document.getElementById(suggestionListId); + + window.searchInput = searchInput; + window.suggestionList = suggestionList; + window.onSelect = onSelect; + + if (!searchInput || !suggestionList) return; + + let timeoutId = null; + + let searchOngoing = false; + searchInput.addEventListener('input', function () { + console.log('input', this.value); + clearTimeout(timeoutId); + const query = this.value.trim(); + + + timeoutId = setTimeout(() => { + if (!searchOngoing) { + searchOngoing = true; + performSearch(query); + searchOngoing = false; + } + }, 300); + }); + +} + + +function performSearch(query) { + console.log('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) { + console.log('displaySuggestions', suggestions); + clearSuggestions(); + suggestions.forEach(suggestion => { + const li = document.createElement('li'); + li.className = 'list-group-item p-2'; + li.textContent = `${suggestion.name} (${suggestion.postcode})`; + li.addEventListener('click', () => { + searchInput.value = suggestion.name; + clearSuggestions(); + if (onSelect) onSelect(suggestion); + }); + window.suggestionList.appendChild(li); + }); + + window.suggestionList.classList.remove('d-none'); + console.log('window.suggestionList', window.suggestionList); +} + +function clearSuggestions() { + window.suggestionList.innerHTML = ''; +} + +// Fermer les suggestions en cliquant en dehors +document.addEventListener('click', function (e) { + if (window.searchInput && !window.searchInput?.contains(e.target) && !window.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 + */ +function getLabourerUrl(obj) { + + return `/admin/labourer/${obj.insee}`; +} + +// Fonction pour gérer la soumission du formulaire d'ajout de ville +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') + */ +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') + */ +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 + */ +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; + } + + const form = document.getElementById('editLand'); + const fields = { + 'name': { + required: true, + message: 'Le nom est requis' + }, + 'contact:street': { + required: true, + message: 'La rue est requise' + }, + 'contact:housenumber': { + required: true, + message: 'Le numéro est requis' + }, + 'contact:phone': { + pattern: /^(?:(?:\+|00)33|0)\s*[1-9](?:[\s.-]*\d{2}){4}$/, + message: 'Le numéro de téléphone n\'est pas valide' + }, + 'contact:email': { + pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, + message: 'L\'adresse email n\'est pas valide' + }, + 'contact:website': { + pattern: /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/, + message: 'L\'URL du site web n\'est pas valide' + } + }; + + let isValid = true; + const errorMessages = {}; + + // Supprimer les messages d'erreur précédents + document.querySelectorAll('.error-message').forEach(el => el.remove()); + + // Vérifier chaque champ + for (const [fieldName, rules] of Object.entries(fields)) { + const input = form.querySelector(`[name="${fieldName}"]`); + if (!input) continue; + + const value = input.value.trim(); + let fieldError = null; + + // Ne valider que si le champ n'est pas vide + if (value) { + if (rules.pattern && !rules.pattern.test(value)) { + fieldError = rules.message; + } + } else if (rules.required) { + // Si le champ est vide et requis + fieldError = rules.message; + } + + if (fieldError) { + isValid = false; + errorMessages[fieldName] = fieldError; + + // Créer et afficher le message d'erreur + const errorDiv = document.createElement('div'); + errorDiv.className = 'error-message text-danger small mt-1'; + errorDiv.textContent = fieldError; + input.parentNode.appendChild(errorDiv); + + // Ajouter une classe d'erreur au champ + input.classList.add('is-invalid'); + } else { + input.classList.remove('is-invalid'); + } + } + + return isValid; +} + +// Exporter les fonctions dans window +window.setupCitySearch = setupCitySearch; +window.getLabourerUrl = getLabourerUrl; +window.handleAddCityFormSubmit = handleAddCityFormSubmit; +window.colorizePercentageCells = colorizePercentageCells; +window.colorizePercentageCellsRelative = colorizePercentageCellsRelative; +window.adjustListGroupFontSize = adjustListGroupFontSize; +window.check_validity = check_validity; +window.enableLabourageForm = enableLabourageForm; +window.performSearch = performSearch; window.openInPanoramax = openInPanoramax; window.listChangesets = listChangesets; window.updateMapHeightForLargeScreens = updateMapHeightForLargeScreens; diff --git a/public/js/app.js b/public/js/app.js deleted file mode 100644 index e69de29..0000000 diff --git a/public/js/utils.js b/public/js/utils.js deleted file mode 100644 index a75b7f1..0000000 --- a/public/js/utils.js +++ /dev/null @@ -1,166 +0,0 @@ -// 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 - */ -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 - */ -function getLabourerUrl(zipCode) { - return `/admin/labourer/${zipCode}`; -} - -// Fonction pour gérer la soumission du formulaire d'ajout de ville -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') - */ -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') - */ -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 - */ -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 89782b9..dc2479a 100644 --- a/src/Controller/AdminController.php +++ b/src/Controller/AdminController.php @@ -39,7 +39,7 @@ final class AdminController extends AbstractController // Récupérer les stats existantes pour la zone $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]); - + $urls = $stats->getAllCTCUrlsMap(); $statsHistory = $this->entityManager->getRepository(StatsHistory::class) ->createQueryBuilder('sh') @@ -90,6 +90,7 @@ final class AdminController extends AbstractController 'maptiler_token' => $_ENV['MAPTILER_TOKEN'], 'mapbox_token' => $_ENV['MAPBOX_TOKEN'], 'statsHistory' => $statsHistory, + 'CTC_urls' => $urls, ]); } diff --git a/src/Entity/Stats.php b/src/Entity/Stats.php index 4278af8..2295028 100644 --- a/src/Entity/Stats.php +++ b/src/Entity/Stats.php @@ -86,6 +86,87 @@ class Stats #[ORM\Column(nullable: true)] private ?int $avec_name = null; + public function getCTCurlBase(): ?string + { + $base = 'https://complete-tes-commerces.fr/'; + $zone = $this->zone; + + $departement = substr($zone, 0, 2); + $insee_code = $zone; + $slug = strtolower(str_replace(' ', '-', $this->getName())); + $url = $base . $departement . '/' . $insee_code . '-'.$slug.'/json/' . $slug ; + + return $url; + } + + public function getLastStats(): string + { + return $this->getParametricJsonFromCTC('_last_stats'); + } + + public function getDailyStats(): string + { + return $this->getParametricJsonFromCTC('_dailystats'); + } + + public function getOSMClosedSirets(): string + { + return $this->getParametricJsonFromCTC('_osm_closed_siret'); + } + + public function getOSMDisusedShops(): string + { + return $this->getParametricJsonFromCTC('_osm_disused_shops'); + } + + public function getOSMNoSiteShops(): string + { + return $this->getParametricJsonFromCTC('_osm_no_site_shops'); + } + + public function getPanoFile(): string + { + return $this->getParametricJsonFromCTC('_pano_file'); + } + + public function getClosedPanoFile(): string + { + return $this->getParametricJsonFromCTC('_closed_pano_file'); + } + + public function getSireneMatches(): string + { + return $this->getParametricJsonFromCTC('_sirene_matches'); + } + + + + public function getParametricJsonFromCTC($suffixe): string + { + $url = $this->getCTCurlBase().$suffixe.'.json'; + + return $url; + } + + + public function getAllCTCUrlsMap(): ?array + { + $functions = [ + 'getLastStats', + 'getDailyStats', + 'getOSMClosedSirets', + 'getOSMDisusedShops', + 'getOSMNoSiteShops', + ]; + + $urls = []; + foreach ($functions as $function) { + $urls[$function] = $this->$function(); + } + return $urls; + } + + // calcule le pourcentage de complétion de la zone public function computeCompletionPercent(): ?int { diff --git a/templates/admin/stats.html.twig b/templates/admin/stats.html.twig index 0822950..753e5b4 100644 --- a/templates/admin/stats.html.twig +++ b/templates/admin/stats.html.twig @@ -22,163 +22,6 @@ {% endblock %} -{% block javascripts %} - {{ parent() }} - - - - - -{% endblock %} {% block body %}
- - Rechercher une ville pour labourer ses commerces -
- -+ + Rechercher une ville pour labourer ses commerces +
+ +