document.addEventListener("DOMContentLoaded", function () { let form = document.getElementById('multiStepForm'); let formTabs = document.querySelectorAll('#formStepper .nav-link'); let nextButtons = document.querySelectorAll('.next-step'); let prevButtons = document.querySelectorAll('.prev-step'); let tabs = document.querySelectorAll("#formStepper .nav-link"); function toggleGroup(radioName, groupSelector, inputSelectors, labelSelectors) { const isCheckedYes = document.querySelector(`input[name="${radioName}"]:checked`)?.value === '1'; const group = document.querySelector(groupSelector); if (!group) return; if (isCheckedYes) { group.style.display = 'flex'; inputSelectors.forEach(sel => { const input = document.querySelector(sel); if (input) input.setAttribute('required', 'required'); }); labelSelectors.forEach(sel => { const label = document.querySelector(sel); if (label) label.classList.add('required'); }); } else { group.style.display = 'none'; inputSelectors.forEach(sel => { const input = document.querySelector(sel); if (input) input.removeAttribute('required'); }); labelSelectors.forEach(sel => { const label = document.querySelector(sel); if (label) label.classList.remove('required'); }); } } // Contribution const contributionRadios = document.querySelectorAll('input[name="contact_investissement_immo[contribution]"]'); contributionRadios.forEach(radio => { radio.addEventListener('change', () => { toggleGroup( 'contact_investissement_immo[contribution]', '.contribution-group', [ 'input[name="contact_investissement_immo[amountContribution]"]', ], [ 'label[for="contact_investissement_immo_amountContribution"]', ] ); }); }); // Loan const bankLoanRadios = document.querySelectorAll('input[name="contact_investissement_immo[bankLoan]"]'); bankLoanRadios.forEach(radio => { radio.addEventListener('change', () => { toggleGroup( 'contact_investissement_immo[bankLoan]', '.loan-group', [ 'input[name="contact_investissement_immo[amountLoan]"]', 'input[name="contact_investissement_immo[durationLoan]"]', 'input[name="contact_investissement_immo[rateLoan]"]' ], [ 'label[for="contact_investissement_immo_amountLoan"]', 'label[for="contact_investissement_immo_durationLoan"]', 'label[for="contact_investissement_immo_rateLoan"]' ] ); }); }); // Agency Fees const agencyRadios = document.querySelectorAll('input[name="contact_investissement_immo[agencyFees]"]'); agencyRadios.forEach(radio => { radio.addEventListener('change', () => { toggleGroup( 'contact_investissement_immo[agencyFees]', '.agency-group', ['input[name="contact_investissement_immo[agencyFeesAmount]"]'], ['label[for="contact_investissement_immo_agencyFeesAmount"]'] ); }); }); // Notary Fees const notaryRadios = document.querySelectorAll('input[name="contact_investissement_immo[notaryFees]"]'); notaryRadios.forEach(radio => { radio.addEventListener('change', () => { toggleGroup( 'contact_investissement_immo[notaryFees]', '.notary-group', // <-- tu devrais renommer ce groupe `.notary-group` si tu veux plus de clarté ['input[name="contact_investissement_immo[notaryFeesAmount]"]'], ['label[for="contact_investissement_immo_notaryFeesAmount"]'] ); }); }); // Work const workRadios = document.querySelectorAll('input[name="contact_investissement_immo[work]"]'); workRadios.forEach(radio => { radio.addEventListener('change', () => { toggleGroup( 'contact_investissement_immo[work]', '.work-group', // <-- pareil ici : renommer en `.work-group` ? ['input[name="contact_investissement_immo[amountsWork]"]'], ['label[for="contact_investissement_immo_amountsWork"]'] ); }); }); // Init on page load toggleGroup( 'contact_investissement_immo[contribution]', '.contribution-group', ['input[name="contact_investissement_immo[amountContribution]"]'], ['label[for="contact_investissement_immo_amountContribution"]'] ); toggleGroup( 'contact_investissement_immo[bankLoan]', '.loan-group', [ 'input[name="contact_investissement_immo[amountLoan]"]', 'input[name="contact_investissement_immo[durationLoan]"]', 'input[name="contact_investissement_immo[rateLoan]"]' ], [ 'label[for="contact_investissement_immo_amountLoan"]', 'label[for="contact_investissement_immo_durationLoan"]', 'label[for="contact_investissement_immo_rateLoan"]' ] ); toggleGroup( 'contact_investissement_immo[agencyFees]', '.agency-group', ['input[name="contact_investissement_immo[agencyFeesAmount]"]'], ['label[for="contact_investissement_immo_agencyFeesAmount"]'] ); toggleGroup( 'contact_investissement_immo[notaryFees]', '.notary-group', ['input[name="contact_investissement_immo[notaryFeesAmount]"]'], ['label[for="contact_investissement_immo_notaryFeesAmount"]'] ); toggleGroup( 'contact_investissement_immo[work]', '.work-group', ['input[name="contact_investissement_immo[amountsWork]"]'], ['label[for="contact_investissement_immo_amountsWork"]'] ); // gestion nombre de biens const numberInput = document.querySelector('#contact_investissement_immo_numberProperties'); const containerPropertiesImmo = document.querySelector('#form_propertiesImmo'); const prototypePropertiesImmo = containerPropertiesImmo.dataset.prototype; function updateCollectionPropertiesImmo(desiredCount) { const current = containerPropertiesImmo.children.length; if (desiredCount > current) { for (let i = current; i < desiredCount; i++) { const newForm = prototypePropertiesImmo.replace(/__property__/g, i); const wrapper = document.createElement('div'); wrapper.innerHTML = newForm; const newElement = wrapper.firstElementChild; newElement.classList.add('collection-item'); // Crée le titre "Bien n°X" const title = document.createElement('h3'); title.classList.add('property-title'); title.textContent = `Bien n°${i + 1}`; newElement.prepend(title); // Injecte le numéro du bien dans l'input hidden "propertyNumber" const input = newElement.querySelector('input[name$="[propertyNumber]"]'); if (input) { input.value = i + 1; // numérotation 1-based } containerPropertiesImmo.appendChild(newElement); // Applique la logique de formatage sur les nouveaux champs .formatted-number const newFormattedInputs = newElement.querySelectorAll('input.formatted-number'); newFormattedInputs.forEach(inputNumber => { // Format initial inputNumber.value = formatNumber(unformatNumber(inputNumber.value)); // Mise à jour du format à chaque saisie inputNumber.addEventListener('input', () => { const cursorPosition = inputNumber.selectionStart; const oldLength = inputNumber.value.length; const unformatted = unformatNumber(inputNumber.value); if (isNaN(unformatted)) return; inputNumber.value = formatNumber(unformatted); // Réajuste la position du curseur const newLength = inputNumber.value.length; const offset = newLength - oldLength; inputNumber.setSelectionRange(cursorPosition + offset, cursorPosition + offset); }); // Bloque les caractères non numériques (et autorise la virgule) inputNumber.addEventListener('beforeinput', (e) => { if (e.data && !/^[0-9,]$/.test(e.data)) { e.preventDefault(); } }); // Nettoie le contenu collé if (!inputNumber.hasAttribute('data-paste-listener-attached')) { // Nettoie le contenu collé inputNumber.addEventListener('paste', (e) => { e.preventDefault(); // Récupère le texte copié et enlève les espaces insécables avant de le coller const pasted = (e.clipboardData || window.clipboardData).getData('text'); // Remplace les espaces insécables par un caractère de séparation (espace ordinaire) const cleaned = pasted.replace(/[^0-9,]/g, ''); // On laisse uniquement les chiffres et la virgule document.execCommand('insertText', false, cleaned); }); // Marque l'input comme ayant un listener paste attaché inputNumber.setAttribute('data-paste-listener-attached', 'true'); } }); } } else if (desiredCount < current) { for (let i = current; i > desiredCount; i--) { containerPropertiesImmo.removeChild(containerPropertiesImmo.lastElementChild); } } // Mise à jour de tous les propertyNumber et titres (en cas de retrait/ajout) const allItems = containerPropertiesImmo.querySelectorAll('.collection-item'); allItems.forEach((item, index) => { const title = item.querySelector('.property-title'); if (title) { title.textContent = `Bien n°${index + 1}`; } const input = item.querySelector('input[name$="[propertyNumber]"]'); if (input) { input.value = index + 1; } }); } numberInput.addEventListener('input', function () { const value = parseInt(this.value, 10); if (!isNaN(value) && value >= 0) { updateCollectionPropertiesImmo(value); updateCollectionPropertiesCharges(value) updateCollectionPropertiesRent(value) } }); // Initialisation si une valeur est déjà présente updateCollectionPropertiesImmo(parseInt(numberInput.value || 0, 10)); const containerCharges = document.querySelector('#form_propertiesCharges'); const prototypeCharges = containerCharges.dataset.prototype; function updateCollectionPropertiesCharges(desiredCount) { const current = containerCharges.children.length; if (desiredCount > current) { for (let i = current; i < desiredCount; i++) { const newForm = prototypeCharges.replace(/__property__/g, i); const wrapper = document.createElement('div'); wrapper.innerHTML = newForm; const newElement = wrapper.firstElementChild; // Injecte automatiquement le numéro du bien dans l’input hidden "propertyNumber" const input = newElement.querySelector('input[name$="[propertyNumber]"]'); if (input) { input.value = i + 1; // numérotation 1-based } // ➕ Création du titre "Bien n°X" const title = document.createElement('h3'); title.textContent = `Bien n°${i + 1}`; title.classList.add('property-title'); // Trouve le container des charges const chargesContainer = newElement.querySelector('.charges-collection'); // ➕ Ajoute les charges par défaut const defaultCharges = ['Taxe foncière', 'Eau', 'Electricité', 'Assurance']; defaultCharges.forEach(chargeName => { addChargeField(chargesContainer, chargeName); }); // ➕ Ajout du bouton "Ajouter une charge" const addChargeBtn = document.createElement('button'); addChargeBtn.type = 'button'; addChargeBtn.textContent = 'Ajouter une charge'; addChargeBtn.classList.add('btn', 'r-10', 'btn--theme', 'hover--black'); addChargeBtn.addEventListener('click', () => { addChargeField(chargesContainer); }); // Injection finale const block = document.createElement('div'); block.appendChild(title); block.appendChild(newElement); block.appendChild(addChargeBtn); block.classList.add('collection-item'); containerCharges.appendChild(block); } } else if (desiredCount < current) { for (let i = current; i > desiredCount; i--) { containerCharges.removeChild(containerCharges.lastElementChild); } } // 🔄 Mise à jour des propertyNumber const allItems = containerCharges.querySelectorAll('input[name$="[propertyNumber]"]'); allItems.forEach((input, index) => { input.value = index + 1; }); } // Fonction pour ajouter une nouvelle charge dans une propriété function addChargeField(container, name = '') { const index = container.children.length; const prototype = container.dataset.prototype; const newForm = prototype.replace(/__name__/g, index); const wrapper = document.createElement('div'); wrapper.innerHTML = newForm; const newElement = wrapper.firstElementChild; newElement.classList.add('row', 'mb-2'); const fieldWrapper = newElement.firstElementChild; if (fieldWrapper) { fieldWrapper.classList.add('d-flex', 'gap-2'); const formGroups = fieldWrapper.querySelectorAll('.form-group'); formGroups.forEach(group => group.classList.add('w-100')); // Ajout du bouton de suppression const deleteButton = document.createElement('button'); deleteButton.type = 'button'; deleteButton.classList.add('btn', 'btn-outline-danger', 'btn-sm'); deleteButton.innerHTML = 'Supprimer'; // Quand on clique, on supprime le bloc parent deleteButton.addEventListener('click', () => { container.removeChild(newElement); }); // Ajout du bouton à la fin du wrapper const deleteWrapper = document.createElement('div'); deleteWrapper.classList.add('form-group'); deleteWrapper.appendChild(deleteButton); fieldWrapper.appendChild(deleteWrapper); } // Pré-remplit le champ "name" si fourni const nameInput = newElement.querySelector('input[name$="[name]"]'); if (nameInput && name) { nameInput.value = name; } // Sélectionne l'input montant et transforme en type="number" const amountInput = newElement.querySelector('input[name$="[amount]"]'); if (amountInput) { // Remplacer le type par text pour permettre le formatage amountInput.type = 'text'; // Permet d'utiliser le formatage avec des espaces amountInput.inputMode = 'numeric'; // Ajoute un clavier numérique amountInput.pattern = '[0-9,]+'; // Permet la saisie de chiffres et de la virgule amountInput.min = 0; amountInput.step = 'any'; // Applique le formatage à l'input montant amountInput.value = formatNumber(unformatNumber(amountInput.value)); // Mise à jour du format à chaque saisie amountInput.addEventListener('input', (e) => { const cursorPosition = amountInput.selectionStart; const oldLength = amountInput.value.length; const unformatted = unformatNumber(amountInput.value); if (isNaN(unformatted)) return; amountInput.value = formatNumber(unformatted); // Réajuste la position du curseur const newLength = amountInput.value.length; const offset = newLength - oldLength; amountInput.setSelectionRange(cursorPosition + offset, cursorPosition + offset); }); // Bloque les caractères non numériques (et autorise la virgule) amountInput.addEventListener('beforeinput', (e) => { if (e.data && !/^[0-9,]$/.test(e.data)) { e.preventDefault(); } }); // Nettoie le contenu collé if (!amountInput.hasAttribute('data-paste-listener-attached')) { // Nettoie le contenu collé amountInput.addEventListener('paste', (e) => { e.preventDefault(); // Récupère le texte copié et enlève les espaces insécables avant de le coller const pasted = (e.clipboardData || window.clipboardData).getData('text'); // Remplace les espaces insécables par un caractère de séparation (espace ordinaire) const cleaned = pasted.replace(/[^0-9,]/g, ''); // On laisse uniquement les chiffres et la virgule document.execCommand('insertText', false, cleaned); }); // Marque l'input comme ayant un listener paste attaché amountInput.setAttribute('data-paste-listener-attached', 'true'); } } container.appendChild(newElement); } updateCollectionPropertiesCharges(parseInt(numberInput.value || 0, 10)); const containerPropertiesRent = document.querySelector('#form_propertiesRent'); const prototypePropertiesRent = containerPropertiesRent.dataset.prototype; function updateCollectionPropertiesRent(desiredCount) { const current = containerPropertiesRent.children.length; if (desiredCount > current) { for (let i = current; i < desiredCount; i++) { const newForm = prototypePropertiesRent.replace(/__property__/g, i); const wrapper = document.createElement('div'); wrapper.innerHTML = newForm; const newElement = wrapper.firstElementChild; newElement.classList.add('collection-item'); // Crée le titre "Bien n°X" const title = document.createElement('h3'); title.classList.add('property-title'); title.textContent = `Bien n°${i + 1}`; newElement.prepend(title); // Injecte le numéro du bien dans l'input hidden "propertyNumber" const input = newElement.querySelector('input[name$="[propertyNumber]"]'); if (input) { input.value = i + 1; // numérotation 1-based } // S'assure que le champ de loyer est un input de type number const rentInput = newElement.querySelector('input[name$="[rentAmount]"]'); if (rentInput) { console.log(rentInput); // Remplacer le type par text pour permettre le formatage rentInput.type = 'text'; // Permet d'utiliser le formatage avec des espaces rentInput.inputMode = 'numeric'; // Ajoute un clavier numérique rentInput.pattern = '[0-9,]+'; // Permet la saisie de chiffres et de la virgule rentInput.min = 0; rentInput.step = 'any'; // Applique le formatage à l'input montant rentInput.value = formatNumber(unformatNumber(rentInput.value)); // Mise à jour du format à chaque saisie rentInput.addEventListener('input', (e) => { const cursorPosition = rentInput.selectionStart; const oldLength = rentInput.value.length; const unformatted = unformatNumber(rentInput.value); if (isNaN(unformatted)) return; rentInput.value = formatNumber(unformatted); // Réajuste la position du curseur const newLength = rentInput.value.length; const offset = newLength - oldLength; rentInput.setSelectionRange(cursorPosition + offset, cursorPosition + offset); }); // Bloque les caractères non numériques (et autorise la virgule) rentInput.addEventListener('beforeinput', (e) => { if (e.data && !/^[0-9,]$/.test(e.data)) { e.preventDefault(); } }); // Nettoie le contenu collé if (!rentInput.hasAttribute('data-paste-listener-attached')) { // Nettoie le contenu collé rentInput.addEventListener('paste', (e) => { e.preventDefault(); // Récupère le texte copié et enlève les espaces insécables avant de le coller const pasted = (e.clipboardData || window.clipboardData).getData('text'); // Remplace les espaces insécables par un caractère de séparation (espace ordinaire) const cleaned = pasted.replace(/[^0-9,]/g, ''); // On laisse uniquement les chiffres et la virgule document.execCommand('insertText', false, cleaned); }); // Marque l'input comme ayant un listener paste attaché rentInput.setAttribute('data-paste-listener-attached', 'true'); } rentInput.classList.add('euro'); } containerPropertiesRent.appendChild(newElement); } } else if (desiredCount < current) { for (let i = current; i > desiredCount; i--) { containerPropertiesRent.removeChild(containerPropertiesRent.lastElementChild); } } // Mise à jour de tous les propertyNumber et titres (en cas de retrait/ajout) const allItems = containerPropertiesRent.querySelectorAll('.collection-item'); allItems.forEach((item, index) => { const title = item.querySelector('.property-title'); if (title) { title.textContent = `Bien n°${index + 1}`; } const input = item.querySelector('input[name$="[propertyNumber]"]'); if (input) { input.value = index + 1; } }); } updateCollectionPropertiesRent(parseInt(numberInput.value || 0, 10)); // Désactiver la navigation manuelle tabs.forEach(tab => { tab.removeAttribute("data-bs-toggle"); tab.addEventListener("click", function (event) { event.preventDefault(); }); }); tippy('.tippy-notary', { content: '
En moyenne, compte entre
7 % et 8 %du prix d’achat dans l’ancien et
2 % à 3 %dans le neuf.
', allowHTML: true, placement: 'right', interactive: true, }); flatpickr(".datepicker", { dateFormat: "d/m/Y", // Format européen allowInput: true, // Permet de taper une date manuellement onReady: function (selectedDates, dateStr, instance) { let input = instance.input; // Ajoute le placeholder personnalisé input.setAttribute("placeholder", "JJ/MM/AA"); // Ajoute une classe CSS pour l'icône input.classList.add("flatpickr-with-icon"); } }); const rateNumber = document.querySelectorAll('input.formatted-taux'); rateNumber.forEach(input => { input.addEventListener('input', (e) => { const original = input.value; // On garde uniquement chiffres, virgule, point const cleaned = original.replace(/[^0-9.,]/g, ''); if (original !== cleaned) { input.value = cleaned; } }); if (!input.hasAttribute('data-paste-listener-attached')) { input.addEventListener('paste', (e) => { e.preventDefault(); const pasted = (e.clipboardData || window.clipboardData).getData('text'); const cleaned = pasted.replace(/[^0-9.,]/g, ''); document.execCommand('insertText', false, cleaned); }); input.setAttribute('data-paste-listener-attached', 'true'); } }); const inputsNumber = document.querySelectorAll('input.formatted-number'); inputsNumber.forEach(inputNumber => { // Format initial inputNumber.value = formatNumber(unformatNumber(inputNumber.value)); // Mise à jour du format à chaque saisie inputNumber.addEventListener('input', () => { const cursorPosition = inputNumber.selectionStart; const oldLength = inputNumber.value.length; const unformatted = unformatNumber(inputNumber.value); if (isNaN(unformatted)) return; inputNumber.value = formatNumber(unformatted); // Réajuste la position du curseur const newLength = inputNumber.value.length; const offset = newLength - oldLength; inputNumber.setSelectionRange(cursorPosition + offset, cursorPosition + offset); }); // Bloque les caractères non numériques (et autorise la virgule) inputNumber.addEventListener('beforeinput', (e) => { if (e.data && !/^[0-9,]$/.test(e.data)) { e.preventDefault(); } }); // Nettoie le contenu collé if (!inputNumber.hasAttribute('data-paste-listener-attached')) { // Nettoie le contenu collé inputNumber.addEventListener('paste', (e) => { e.preventDefault(); // Récupère le texte copié et enlève les espaces insécables avant de le coller const pasted = (e.clipboardData || window.clipboardData).getData('text'); // Remplace les espaces insécables par un caractère de séparation (espace ordinaire) const cleaned = pasted.replace(/[^0-9,]/g, ''); // On laisse uniquement les chiffres et la virgule document.execCommand('insertText', false, cleaned); }); // Marque l'input comme ayant un listener paste attaché inputNumber.setAttribute('data-paste-listener-attached', 'true'); } }); // Supprime les espaces et convertit la virgule en point function unformatNumber(value) { return value .replace(/\s/g, '') // supprime tous les espaces .replace(',', '.'); // transforme la virgule en point } // Ajoute les espaces comme séparateurs de milliers et remet la virgule function formatNumber(value) { if (!value || isNaN(value)) return ''; let [intPart, decimalPart] = value.toString().split('.'); intPart = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ' '); // espace insécable return decimalPart ? `${intPart},${decimalPart}` : intPart; } const buttons = document.querySelectorAll('.payment-btn'); const paymentMethodInput = document.getElementById('contact_investissement_immo_paymentMethod'); const infoText = document.querySelector('.info'); const paypalContainer = document.getElementById('paypal-button-container'); const messages = { cb: "En cliquant sur le bouton, une fenêtre de notre banque s’ouvrira pour confirmer le paiement.", paypal: "En cliquant sur le bouton, les modes de paiement PayPal s’afficheront.", }; buttons.forEach(button => { button.addEventListener('click', () => { // Met à jour la valeur du champ caché const method = button.dataset.method; paymentMethodInput.value = method; // Retire la classe active de tous les boutons et ajoute à celui sélectionné buttons.forEach(btn => btn.classList.remove('active')); button.classList.add('active'); if (method === 'paypal') { paypalContainer.style.display = 'block'; } else { paypalContainer.style.display = 'none'; } // Met à jour le texte d'information infoText.textContent = messages[method]; }); }); const submitBtn = document.getElementById('submitBtn'); let paypalButtonsRendered = false; submitBtn.addEventListener('click', function (e) { e.preventDefault(); if (validateCurrentStep()) { let selectedMethod = paymentMethodInput.value; if (selectedMethod === 'paypal') { console.log('PayPal selected'); if (!paypalButtonsRendered) { paypal.Buttons({ createOrder: function (data, actions) { return actions.order.create({ purchase_units: [{ amount: { value: '259.00' } }] }); }, onApprove: function (data, actions) { return actions.order.capture().then(function (details) { form.submit(); }); }, onError: function (err) { console.error('Erreur PayPal : ', err); alert('Une erreur est survenue pendant le paiement.'); } }).render('#paypal-button-container'); paypalButtonsRendered = true; submitBtn.disabled = true; // éviter clics multiples } } else { // Paiement autre que PayPal : soumission classique form.submit(); } // form.submit(); } }) const fileInputs = document.querySelectorAll('input[type="file"].custom-file-input'); fileInputs.forEach(input => { const label = document.querySelector(`label[for="${input.id}"]`); if (!label) return; // Skip si pas de label associé // Texte de base label.textContent = 'Aucun fichier sélectionné'; input.addEventListener('change', () => { const fileName = input.files.length > 0 ? input.files[0].name : 'Aucun fichier sélectionné'; label.textContent = fileName; }); }); function getCurrentTabIndex() { return Array.from(formTabs).findIndex(tab => tab.classList.contains('active')); } function updateStepCompletion() { let currentIndex = getCurrentTabIndex(); formTabs.forEach((tab, index) => { let navItem = tab.closest('.nav-item'); let stepNumber = tab.querySelector('.step-number'); if (index < currentIndex) { tab.classList.add('completed'); navItem.classList.add('completed'); stepNumber.innerHTML = ''; // Clear the step number } else { tab.classList.remove('completed'); navItem.classList.remove('completed'); stepNumber.innerHTML = index + 1; // Reset to the step number } }); } function scrollToForm() { const offset = 100; // Ajuste cette valeur selon tes besoins const formTop = form.getBoundingClientRect().top + window.scrollY - offset; window.scrollTo({ top: formTop, behavior: "smooth" }); } function changeTab(index, validate = true) { formTabs.forEach((tab, i) => { tab.classList.remove('active'); tab.setAttribute('aria-selected', 'false'); console.log(tab.getAttribute('href')) document.querySelector(tab.getAttribute('href')).classList.remove('show', 'active'); }); formTabs[index].classList.add('active'); formTabs[index].setAttribute('aria-selected', 'true'); document.querySelector(formTabs[index].getAttribute('href')).classList.add('show', 'active'); if (validate) { updateStepCompletion(); } centerActiveStep(index); // Ajout du centrage updateRecap(); } function validateField(input) { let isValid = true; let errorMessage = ""; let errorSpan; // Supprimer les erreurs existantes let formGroup = input.closest('.form-group'); let existingErrors = formGroup ? formGroup.querySelectorAll(".validation-error") : []; existingErrors.forEach(error => error.remove()); // Validation des boutons radio if (input.type === "radio") { let radioGroup = document.querySelectorAll(`input[name="${input.name}"]`); let isChecked = Array.from(radioGroup).some(radio => radio.checked); if (!isChecked) { isValid = false; errorMessage = "Veuillez sélectionner une option."; if (formGroup && !formGroup.querySelector(".validation-error")) { errorSpan = document.createElement("span"); errorSpan.classList.add("text-danger", "validation-error"); errorSpan.innerText = errorMessage; let legend = formGroup.querySelector("legend"); if (legend) { legend.insertAdjacentElement("afterend", errorSpan); } } } radioGroup.forEach(radio => { radio.classList.toggle("is-invalid", !isValid); }); } else if (input.type === "file") { // Validation pour les champs de type file let files = input.files; if (input.hasAttribute("required") && files.length === 0) { isValid = false; errorMessage = "Veuillez sélectionner un fichier."; } else if (files.length > 0) { // Exemple de validation de type de fichier (ici on autorise les images .jpg et .png) let validFileTypes = ['image/jpeg', 'image/png']; let validFileSize = 5 * 1024 * 1024; // Taille maximale autorisée : 5MB let file = files[0]; // Vérifier la taille du fichier if (file.size > validFileSize) { isValid = false; errorMessage = "Le fichier ne doit pas dépasser 5MB."; } } // Ajout de l'erreur pour le champ de type file if (!isValid) { let error = document.createElement("span"); error.classList.add("text-danger", "validation-error"); error.innerText = errorMessage; input.classList.add("is-invalid"); // Insérer le message d'erreur après la legend let formGroup = input.closest('.form-group'); let legend = formGroup.querySelector("legend"); if (legend) { legend.insertAdjacentElement("afterend", error); } else { // Si il n'y a pas de legend, placer l'erreur juste après l'input input.parentNode.insertBefore(error, input.nextSibling); } } else { input.classList.remove("is-invalid"); } } else if(input.type === "checkbox") { // Validation pour les cases à cocher if (input.hasAttribute("required") && !input.checked) { isValid = false; errorMessage = "Veuillez accepter les conditions."; } if (!isValid) { let error = document.createElement("span"); error.classList.add("text-danger", "validation-error"); error.innerText = errorMessage; input.classList.add("is-invalid"); let formGroup = input.closest('.form-group'); formGroup.appendChild(error); } else { input.classList.remove("is-invalid"); } } else { // Validation des autres champs if (input.hasAttribute("required") && input.value.trim() === "") { isValid = false; errorMessage = "Ce champ est obligatoire."; } else if (input.type === "email" && input.value.trim() !== "") { let emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailPattern.test(input.value)) { isValid = false; errorMessage = "Veuillez entrer un email valide."; } } else if (input.type === "number" && input.value.trim() !== "") { let min = input.min ? parseFloat(input.min) : null; let max = input.max ? parseFloat(input.max) : null; let value = parseFloat(input.value); if (!/^\d+$/.test(input.value)) { isValid = false; errorMessage = "Veuillez entrer un nombre valide."; } else if (min !== null && value < min) { isValid = false; errorMessage = `La valeur doit être au minimum ${min}.`; } else if (max !== null && value > max) { isValid = false; errorMessage = `La valeur doit être au maximum ${max}.`; } } else if (input.dataset.type === "phone" && input.value.trim() !== "") { let phonePattern = /^(\+33|0)[1-9](\d{2}){4}$/; if (!phonePattern.test(input.value)) { isValid = false; errorMessage = "Veuillez entrer un numéro de téléphone valide."; } } if (!isValid) { let error = document.createElement("span"); error.classList.add("text-danger", "validation-error"); error.innerText = errorMessage; input.classList.add("is-invalid"); let flatpickrWrapper = input.closest(".flatpickr-wrapper"); if (flatpickrWrapper) { flatpickrWrapper.parentNode.insertBefore(error, flatpickrWrapper); } else { input.parentNode.insertBefore(error, input); } } else { input.classList.remove("is-invalid"); } } return isValid; } function updateValue(fieldId, value) { // Met à jour la valeur du champ number lié au slider const field = document.querySelector(`[name="contact_investissement_immo[${fieldId}]"]`); if (field) { field.value = value; } } function centerActiveStep(index) { let stepper = document.querySelector('.stepper'); let slides = document.querySelectorAll('.stepper .nav-item'); if (!stepper || slides.length === 0 || index < 0 || index >= slides.length) return; let slideWidth = slides[0].offsetWidth; // On suppose que toutes les slides ont la même largeur let scrollLeft = slideWidth * (index - 1); // Empêcher le dépassement des bords scrollLeft = Math.max(0, Math.min(scrollLeft, stepper.scrollWidth - stepper.clientWidth)); stepper.scrollTo({ left: scrollLeft, behavior: "smooth" }); } function validateCurrentStep() { let currentIndex = getCurrentTabIndex(); let currentStep = document.querySelector(formTabs[currentIndex].getAttribute('href')); let inputs = currentStep.querySelectorAll("input, select, textarea"); let isValid = true; let validatedRadioGroups = new Set(); inputs.forEach(input => { if (input.type === "radio") { if (!validatedRadioGroups.has(input.name)) { validatedRadioGroups.add(input.name); let firstRadio = document.querySelector(`input[name="${input.name}"]`); if (firstRadio && !validateField(firstRadio)) { isValid = false; } } } else { if (!validateField(input)) { isValid = false; } } }); return isValid; } function updateRecap() { // Récupérer les valeurs des champs du formulaire const purchasePrice = document.querySelector('[name="contact_investissement_immo[purchasePrice]"]').value; const contribution = document.querySelector('[name="contact_investissement_immo[contribution]"]:checked')?.value === '1' ? 'Oui' : 'Non'; const amountContribution = document.querySelector('[name="contact_investissement_immo[amountContribution]"]').value || 'N/A'; const bankLoan = document.querySelector('[name="contact_investissement_immo[bankLoan]"]:checked')?.value === '1' ? 'Oui' : 'Non'; const amountLoan = document.querySelector('[name="contact_investissement_immo[amountLoan]"]').value || 'N/A'; const durationLoan = document.querySelector('[name="contact_investissement_immo[durationLoan]"]').value || 'N/A'; const rateLoan = document.querySelector('[name="contact_investissement_immo[rateLoan]"]').value || 'N/A'; const agencyFees = document.querySelector('[name="contact_investissement_immo[agencyFees]"]:checked')?.value === '1' ? 'Oui' : 'Non'; const agencyFeesAmount = document.querySelector('[name="contact_investissement_immo[agencyFeesAmount]"]').value || 'N/A'; const notaryFees = document.querySelector('[name="contact_investissement_immo[notaryFees]"]:checked')?.value === '1' ? 'Oui' : 'Non'; const notaryFeesAmount = document.querySelector('[name="contact_investissement_immo[notaryFeesAmount]"]').value || 'N/A'; const work = document.querySelector('[name="contact_investissement_immo[work]"]:checked')?.value === '1' ? 'Oui' : 'Non'; const amountsWork = document.querySelector('[name="contact_investissement_immo[amountsWork]"]').value || 'N/A'; const businessStructure = document.querySelector('[name="contact_investissement_immo[businessStructure]"]').value; const numberProperties = document.querySelector('[name="contact_investissement_immo[numberProperties]"]').value; // Gestion des collections : propriétés immobilières const propertiesImmo = Array.from(document.querySelectorAll('#form_propertiesImmo .collection-item')).map((item, index) => { const surface = item.querySelector('[name$="[surface]"]').value || 'N/A'; const biens = item.querySelector('[name$="[biens]"]').value || 'N/A'; return `Bien n°${index + 1}: Surface - ${surface} m², Type - ${biens}`; }); // Gestion des collections : charges const propertiesCharges = Array.from(document.querySelectorAll('#form_propertiesCharges .collection-item')).map((item, index) => { const charges = Array.from(item.querySelectorAll('.charges-collection .row')).map(charge => { const name = charge.querySelector('[name$="[name]"]').value || 'N/A'; const amount = charge.querySelector('[name$="[amount]"]').value || 'N/A'; const periodicite = charge.querySelector('[name$="[periodicite]"]').value || 'N/A'; return `Charges pour le bien n°${index + 1}:
Prix d'achat : ${purchasePrice} €
Apport ? : ${contribution}
Montant de l'apport : ${amountContribution} €
Prêt bancaire : ${bankLoan}
Montant du prêt : ${amountLoan} €
Durée du prêt : ${durationLoan} ans
Taux du prêt : ${rateLoan} %
Frais d'agence : ${agencyFees}
Montant des frais d'agence : ${agencyFeesAmount} €
Frais de notaire : ${notaryFees}
Montant des frais de notaire : ${notaryFeesAmount} €
Travaux réalisés : ${work}
Montants des travaux : ${amountsWork} €
Forme juridique : ${businessStructure}
Nombre de biens : ${numberProperties}
Vos biens :
Nombre de parts fiscales : ${numberPartTax}
Revenu fiscal de référence : ${taxReferanceIncome} €
${messageFree}