diff --git a/assets/utils.js b/assets/utils.js index e6cd43a..8b692b7 100644 --- a/assets/utils.js +++ b/assets/utils.js @@ -179,295 +179,9 @@ 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; window.searchInseeCode = searchInseeCode; window.genererCouleurPastel = genererCouleurPastel; -window.check_validity = check_validity; \ No newline at end of file +window.check_validity = check_validity; diff --git a/public/js/app.js b/public/js/app.js new file mode 100644 index 0000000..e69de29 diff --git a/public/js/utils.js b/public/js/utils.js new file mode 100644 index 0000000..a75b7f1 --- /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 + */ +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 dc2479a..89782b9 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,7 +90,6 @@ 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 2295028..4278af8 100644 --- a/src/Entity/Stats.php +++ b/src/Entity/Stats.php @@ -86,87 +86,6 @@ 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 e1c1abe..0822950 100644 --- a/templates/admin/stats.html.twig +++ b/templates/admin/stats.html.twig @@ -22,6 +22,163 @@ {% endblock %} +{% block javascripts %} + {{ parent() }} + + + + + +{% endblock %} {% block body %}
+ + Rechercher une ville pour labourer ses commerces +
+ +- - Rechercher une ville pour labourer ses commerces -
- -