jQuery(function($) { jQuery('form.load_simulator').each(function() { jQuery(this).attr('action', window.location.origin + '/credit-step1') }) /** * Système de sauvegarde localStorage */ const FORM_STORAGE_KEY = 'credit_direct_form_data'; const FORM_STORAGE_TIMESTAMP = 'credit_direct_form_timestamp'; const STORAGE_EXPIRY_HOURS = 24; // Les données expirent après 24h /** * Affiche l'indicateur de sauvegarde */ function showSaveIndicator(message, type = 'success') { let indicator = jQuery('.auto-save-indicator'); if (indicator.length === 0) { indicator = jQuery('
'); jQuery('body').append(indicator); } indicator.removeClass('show saving error').addClass('show ' + type); indicator.text(message); // Masquer après 3 secondes setTimeout(() => { indicator.removeClass('show'); }, 3000); } /** * Sauvegarde les données du formulaire dans localStorage */ function saveFormData() { // Afficher l'indicateur de sauvegarde showSaveIndicator('Sauvegarde en cours...', 'saving'); const formData = {}; const currentStep = getCurrentStep(); // Récupérer tous les champs du formulaire jQuery('form.form-submit').find('input, select, textarea').each(function() { const $field = jQuery(this); const name = $field.attr('name'); const type = $field.attr('type'); if (!name) return; let value = ''; switch (type) { case 'checkbox': value = $field.is(':checked') ? $field.val() : ''; break; case 'radio': if ($field.is(':checked')) { value = $field.val(); } break; case 'file': // Ne pas sauvegarder les fichiers return; default: value = $field.val() || ''; break; } // Ne sauvegarder que si la valeur n'est pas vide if (value !== '') { formData[name] = value; } }); // Sauvegarder avec l'étape actuelle et le timestamp const dataToSave = { step: currentStep, data: formData, timestamp: Date.now() }; try { localStorage.setItem(FORM_STORAGE_KEY, JSON.stringify(dataToSave)); localStorage.setItem(FORM_STORAGE_TIMESTAMP, Date.now().toString()); console.log('Données sauvegardées dans localStorage:', dataToSave); // Afficher le succès setTimeout(() => { showSaveIndicator('Données sauvegardées', 'success'); }, 500); } catch (e) { console.error('Erreur lors de la sauvegarde localStorage:', e); showSaveIndicator('Erreur de sauvegarde', 'error'); } } /** * Restaure les données du formulaire depuis localStorage */ function restoreFormData() { try { const savedData = localStorage.getItem(FORM_STORAGE_KEY); if (!savedData) return false; const parsedData = JSON.parse(savedData); const now = Date.now(); const savedTime = parsedData.timestamp || 0; // Vérifier si les données ne sont pas expirées if (now - savedTime > STORAGE_EXPIRY_HOURS * 60 * 60 * 1000) { console.log('Données localStorage expirées, suppression...'); clearFormData(); return false; } const formData = parsedData.data || {}; let restoredCount = 0; // Restaurer les valeurs Object.keys(formData).forEach(name => { const $field = jQuery('form.form-submit').find(`[name="${name}"]`); if ($field.length === 0) return; const value = formData[name]; const type = $field.attr('type'); switch (type) { case 'checkbox': if (value) { $field.prop('checked', true); } break; case 'radio': $field.filter(`[value="${value}"]`).prop('checked', true); break; default: $field.val(value); break; } // Ajouter un effet visuel temporaire $field.addClass('field-restored'); setTimeout(() => { $field.removeClass('field-restored'); }, 2000); restoredCount++; }); console.log(`${restoredCount} champs restaurés depuis localStorage`); if (restoredCount > 0) { showSaveIndicator(`${restoredCount} champs restaurés`, 'success'); } return restoredCount > 0; } catch (e) { console.error('Erreur lors de la restauration localStorage:', e); return false; } } /** * Efface les données du formulaire du localStorage */ function clearFormData() { try { localStorage.removeItem(FORM_STORAGE_KEY); localStorage.removeItem(FORM_STORAGE_TIMESTAMP); console.log('Données localStorage effacées'); // Masquer le message d'information jQuery('.localstorage-info').fadeOut(); // Afficher un message de confirmation showSaveIndicator('Données effacées', 'success'); } catch (e) { console.error('Erreur lors de l\'effacement localStorage:', e); showSaveIndicator('Erreur lors de l\'effacement', 'error'); } } // Rendre la fonction accessible globalement window.clearFormData = clearFormData; /** * Détermine l'étape actuelle basée sur l'URL */ function getCurrentStep() { const path = window.location.pathname; if (path.includes('credit-step1')) return 'step1'; if (path.includes('credit-step2')) return 'step2'; if (path.includes('credit-step3')) return 'step3'; if (path.includes('credit-step4')) return 'step4'; if (path.includes('credit-step5')) return 'step5'; return 'unknown'; } /** * Sauvegarde automatique lors de la modification des champs */ function setupAutoSave() { // Sauvegarde sur clic du lien "previous-step" jQuery('a.previous-step').on('click', function(e) { /* e.preventDefault(); */ saveFormData(); }); // Sauvegarde sur changement de valeur (désactivé) // jQuery('form.form-submit').on('change input', 'input, select, textarea', function() { // // Debounce pour éviter trop de sauvegardes // clearTimeout(window.autoSaveTimeout); // window.autoSaveTimeout = setTimeout(saveFormData, 1000); // }); // Sauvegarde sur clic des boutons radio/checkbox (désactivé) // jQuery('form.form-submit').on('click', 'input[type="radio"], input[type="checkbox"]', function() { // setTimeout(saveFormData, 100); // }); // Sauvegarde avant soumission du formulaire (désactivé) // jQuery('form.form-submit').on('submit', function() { // saveFormData(); // }); // Sauvegarde avant navigation (bouton étape précédente) (désactivé) // jQuery('a[href*="credit-step"]').on('click', function() { // saveFormData(); // }); // Sauvegarde lors de l'ajout/suppression de crédits en cours (désactivé) // $(document).on('currentLoanAdded currentLoanRemoved', function() { // setTimeout(saveFormData, 500); // }); } /** * Affiche le message d'information localStorage */ function showLocalStorageInfo() { const savedData = localStorage.getItem(FORM_STORAGE_KEY); if (!savedData) return; // Vérifier si le message existe déjà if (jQuery('.localstorage-info').length > 0) return; const infoHtml = `
💾 Vos données sont sauvegardées automatiquement.
`; // Insérer au début du formulaire jQuery('form.form-submit').prepend(infoHtml); } /** * Initialise le système de sauvegarde */ function initFormStorage() { if(hasCreditDirectToken()) return; //check if localStorage is empty if(localStorage.getItem(FORM_STORAGE_KEY) === null) return; // Restaurer les données au chargement const restored = restoreFormData(); // Configurer la sauvegarde automatique setupAutoSave(); // Effacer les données après soumission réussie (optionnel) jQuery(document).on('formSubmitted', function() { clearFormData(); }); // Si des données ont été restaurées, initialiser l'affichage des zones conditionnelles if (restored) { setTimeout(function() { initializePreFilledValues(); }, 200); } // Afficher le message d'information showLocalStorageInfo(); console.log('Système de sauvegarde localStorage initialisé'); } function hasCreditDirectToken() { return window.location.search.includes('credit-direct-token'); } // Initialiser le système de sauvegarde UNIQUEMENT si le paramètre est présent dans l'URL initFormStorage(); if (hasCreditDirectToken()) { } /** * General */ function empty(value) { return value == undefined || value == null || value == [] || value == ''; } function makeLabelRequired(containerSelector = 'body') { if (jQuery(containerSelector).length > 0) { jQuery(containerSelector).find('input:required, select:required, textarea:required').each(function() { var labelId = jQuery(this).attr('id'); var label = jQuery('label[for="' + labelId + '"]') if (label.length > 0 && label.html().charAt(label.html().length - 1) !== '*') { label.html(label.html() + '*') } }) } } if (jQuery('.wpcf-borrower').length > 0) { makeLabelRequired('.wpcf-borrower') } else { makeLabelRequired('.form-submit') } /** * Step 2 */ function changeJobLabels(value, container) { var jobLabels = { independent: { factory: 'de l\'entreprise', salary: 'Revenu imposable annuel', startDate: 'de début d\'activité' }, withoutEmployer: { employer: 'de l\'employeur/organisme de paiement', salary: 'Indemnités mensuelles', startDate: 'd\'engagement' }, withEmployer: { employer: 'de l\'employeur/organisme de paiement', salary: 'Salaire net mensuel', startDate: 'd\'engagement' } }, job = { independent: 5, withoutEmployer: ['1','6','9','12'] }; console.log('job', value, job.independent); var prefix = container.find('#IDcofirstname').length > 0 ? 'co' : '' var fields = ['emname', 'emaddress', 'emzip', 'emcity', 'emcountry', 'commitmentdate', 'salary'] fields.forEach(id => { var labelText = null switch (id) { case 'commitmentdate': if(value === job.independent) { labelText = jobLabels.independent.startDate } else if(job.withoutEmployer.includes(value)) { labelText = jobLabels.withoutEmployer.startDate } else { labelText = jobLabels.withEmployer.startDate } break; case 'salary': if(value === job.independent) { labelText = jobLabels.independent.salary } else if(job.withoutEmployer.includes(value)) { labelText = jobLabels.withoutEmployer.salary } else { labelText = jobLabels.withEmployer.salary } break; default: if(value === job.independent) { labelText = jobLabels.independent.factory } else if(job.withoutEmployer.includes(value)) { labelText = jobLabels.withoutEmployer.employer } else { labelText = jobLabels.withEmployer.employer } break; } container.find('label[for="ID' + prefix + id + '"] > span').text(labelText); console.log('labelText', labelText); }); /* console.log('container', container); console.log('prefix', container.find('.'+prefix+'independent_section')); */ console.log('value', value); if(value == job.independent) { container.find('.'+prefix+'independent_section').fadeIn().removeClass('hidden'); //remove required attributes container.find('.'+prefix+'employee_section').find('input, select').removeAttr('required'); container.find('.'+prefix+'employee_section').fadeOut().addClass('hidden'); container.find('.'+prefix+'independent_section').find('input, select').attr('required', true); } else if(job.withoutEmployer.includes(value)) { container.find('.withemployer_section').fadeOut().addClass('hidden'); container.find('.withemployer_section').find('input, select').removeAttr('required'); console.log((jobLabels.withoutEmployer.salary)); /* container.find('.'+prefix+'IDsalary').prev().text(jobLabels.withoutEmployer.salary); console.log(container.find('#'+prefix+'IDsalary').prev()); */ } else { container.find('.'+prefix+'independent_section').fadeOut().addClass('hidden'); //not input field required container.find('.'+prefix+'employee_section').find('input:not([type="file"]), select').attr('required', true); container.find('.'+prefix+'employee_section').fadeIn().removeClass('hidden'); //except file inputs container.find('.'+prefix+'independent_section').find('input:not([type="file"]), select').removeAttr('required'); if(jQuery('.withemployer_section').hasClass('hidden')) { jQuery('.withemployer_section').fadeIn().removeClass('hidden'); jQuery('.withemployer_section').find('input, select').attr('required', true); } } container .find('#ID' + prefix + 'emnumber') .attr('required', value === job.independent) .parent() .toggleClass('d-none', value !== job.independent) makeLabelRequired() } function displayCoBorrower() { var excludedFieldsName = [ 'cocontract_type', 'coemname', 'coemaddress', 'coemzip', 'coemcity', 'coemcountry', 'cocommitmentdate', 'cooiamouthmealvoucher', 'cooiamouthrentalincome', 'cooiamouthunemployment', 'cooiamouthother', 'cooiothertext', 'co_secteur_activite', 'co_num_tva', 'coMtn_loyer' ], hasCoBorrower = jQuery('input[name="hascoborrower"]:checked').length > 0 && jQuery('input[name="hascoborrower"]:checked').val() == 1, key = '.wpcf-coborrower', cols_key = '.credit-cols'; console.log('hasCoBorrower', hasCoBorrower); // Gestion de l'affichage/masquage jQuery(key).toggleClass('d-none', !hasCoBorrower); // Gestion des colonnes if (hasCoBorrower) { // Mode deux colonnes jQuery(cols_key).removeClass('col-md-12').addClass('col-md-6'); // Ajuster les colonnes internes jQuery('.credit-cols [class*="col-md-"]').each(function() { var classes = jQuery(this).attr('class').split(' '); classes.forEach(function(className) { if (className.startsWith('col-md-')) { var currentSize = parseInt(className.replace('col-md-', '')); console.log('currentSize', currentSize); // Sauvegarder la taille originale si ce n'est pas déjà fait if (!jQuery(this).data('original-size')) { jQuery(this).data('original-size', className); } // Définir la nouvelle taille jQuery(this).removeClass(className).addClass('col-md-12'); } }.bind(this)); }); } else { // Mode une colonne jQuery('.credit-cols .wpcf-borrower').removeClass('col-md-6'); if(!jQuery(cols_key).hasClass('col-md-12')) jQuery(cols_key).addClass('col-md-12').removeClass('col-md-6'); // Restaurer les colonnes internes à leur taille d'origine jQuery('.credit-cols [class*="col-md-"]').each(function() { var originalSize = jQuery(this).data('original-size'); if (originalSize) { // Supprimer toutes les classes col-md-* actuelles var classes = jQuery(this).attr('class').split(' '); classes.forEach(function(className) { if (className.startsWith('col-md-')) { jQuery(this).removeClass(className); } }.bind(this)); // Restaurer la taille originale jQuery(this).addClass(originalSize); } }); } // Gestion des champs requis jQuery(key).find('input[type="text"], input[type="number"], input[type="date"], select, textarea').each(function() { if (excludedFieldsName.indexOf(jQuery(this).attr('name')) < 0) { jQuery(this).attr('required', hasCoBorrower) } }) if (!hasCoBorrower) { jQuery(key).find('input[type="checkbox"]:checked, input[type="radio"][value="0"]').each(function() { jQuery(this).prop('checked', jQuery(this).attr('type') === 'radio') }) jQuery(key).find('input:not([type="checkbox"]):not([type="radio"]), select, textarea').each(function() { jQuery(this).val('') }) } makeLabelRequired(key) displayOtherIncome( hasCoBorrower && jQuery('input[name="cohasotherincome"]:checked').length > 0 && jQuery('input[name="cohasotherincome"]:checked').val() == 1, '.co' ) } function displayOtherIncome(show, key) { var target = jQuery('.wpcf-otherincome' + key) if (target.length > 0) { target.toggleClass('d-none', !show); if (!show) { target.find('input[type="checkbox"]:checked, input[type="radio"][value="0"]').each(function() { jQuery(this).prop('checked', jQuery(this).attr('type') === 'radio') }) } displayOtherIncomeSubFields(target.find('input[type="checkbox"], input[type="radio"]:checked')) } } function displayOtherIncomeSubFields(el) { el.each(function() { var subFields = jQuery(this).closest('.form-row').find('.form-group') if (subFields.length > 0) { subFields.toggleClass('d-none', !jQuery(this).is(':checked')) subFields.find('input').attr('required', jQuery(this).is(':checked')) } }) } function initializePreFilledValues() { // Gérer l'affichage des sections basé sur les valeurs pré-remplies setTimeout(function() { // Vérifier si hascoborrower est pré-rempli var hasCoBorrowerChecked = jQuery('input[name="hascoborrower"]:checked'); if (hasCoBorrowerChecked.length > 0) { displayCoBorrower(); } // Vérifier si hasotherincome est pré-rempli (emprunteur) var hasOtherIncomeChecked = jQuery('input[name="hasotherincome"]:checked'); if (hasOtherIncomeChecked.length > 0) { displayOtherIncome(hasOtherIncomeChecked.val() == 1, '.em'); } // Vérifier si cohasotherincome est pré-rempli (co-emprunteur) var coHasOtherIncomeChecked = jQuery('input[name="cohasotherincome"]:checked'); if (coHasOtherIncomeChecked.length > 0) { displayOtherIncome(coHasOtherIncomeChecked.val() == 1, '.co'); } // Vérifier si hascurrentloan est pré-rempli var hasCurrentLoanChecked = jQuery('input[name="hascurrentloan"]:checked'); if (hasCurrentLoanChecked.length > 0) { var isChecked = hasCurrentLoanChecked.val() == '1'; jQuery('.wpcf-currentloan').toggleClass('d-none', !isChecked); } // Vérifier si cohascurrentloan est pré-rempli var coHasCurrentLoanChecked = jQuery('input[name="cohascurrentloan"]:checked'); if (coHasCurrentLoanChecked.length > 0) { var isChecked = coHasCurrentLoanChecked.val() == '1'; jQuery('.wpcf-cocurrentloan').toggleClass('d-none', !isChecked); } // Gérer les champs autres revenus pré-remplis jQuery('.wpcf-otherincome input[type="checkbox"]:checked').each(function() { displayOtherIncomeSubFields(jQuery(this)); }); // Gérer les champs indépendant pré-remplis var jobSelect = jQuery('select[name="job"]'); if (jobSelect.length > 0 && jobSelect.val()) { changeJobLabels(jobSelect.val(), jobSelect.closest('fieldset')); } var coJobSelect = jQuery('select[name="cojob"]'); if (coJobSelect.length > 0 && coJobSelect.val()) { changeJobLabels(coJobSelect.val(), coJobSelect.closest('fieldset')); } }, 100); // Petit délai pour s'assurer que le DOM est chargé } var hasCoBorrowerField = jQuery('input[name="hascoborrower"]:checked') if (hasCoBorrowerField.length > 0) { displayCoBorrower() } if (jQuery('input[name="hasotherincome"]:checked').length > 0) { displayOtherIncome(jQuery('input[name="hasotherincome"]:checked').val() == 1, '.em') } // Initialisation pour les valeurs pré-remplies initializePreFilledValues(); jQuery('body').on('change', 'input[name="hascoborrower"]', displayCoBorrower); jQuery('body').on('change', 'input[name="hasotherincome"]', function() { displayOtherIncome(jQuery(this).val() == 1, '.em') }); jQuery('body').on('change', 'input.wpcf-otherincome--oitype', function(e) { displayOtherIncomeSubFields(jQuery(e.target)) }); jQuery('body').on('change', 'input[name="cohasotherincome"]', function() { displayOtherIncome(jQuery(this).val() == 1, '.co') }); jQuery('body').on('change', 'select[name="job"], select[name="cojob"]', function() { changeJobLabels(jQuery(this).val(), jQuery(this).closest('fieldset')) }); /** * Step 3 */ jQuery('body').on('change', 'select[name="hometype"], select[name="cohometype"]', function(e) { var val = jQuery(this).val(), c = 'd-none', cname = 'wpcf-' + jQuery(this).attr('name') + '--complement-home' + val, elements = jQuery('.wpcf-' + jQuery(this).attr('name') + '--complement .form-group'); elements.addClass(c); elements.each(function() { if (jQuery(this).hasClass(cname)) { jQuery('.' + cname).removeClass(c); } jQuery(this).find('input, select').attr('required', !jQuery(this).hasClass(c)) makeLabelRequired(jQuery(this).context) }) }); var currentLoanProto = jQuery('.wpcf-currentloan--block').length > 0 ? jQuery('.wpcf-currentloan--block') : null; var currentLoanCount = 0; if (currentLoanProto !== null) { currentLoanProto.removeClass('wpcf-prototype'); currentLoanProto = currentLoanProto[0].outerHTML jQuery('.wpcf-currentloan--block').remove() } var coCurrentLoanProto = jQuery('.wpcf-cocurrentloan--block').length > 0 ? jQuery('.wpcf-cocurrentloan--block') : null; var coCurrentLoanCount = 0; if (coCurrentLoanProto !== null) { coCurrentLoanProto.removeClass('wpcf-prototype'); coCurrentLoanProto = coCurrentLoanProto[0].outerHTML jQuery('.wpcf-cocurrentloan--block').remove() } jQuery('body').on('change', 'input[name="hascurrentloan"], input[name="cohascurrentloan"]', function(e) { var selector = jQuery(this).attr('name') === 'hascurrentloan' ? '.wpcf-currentloan' : '.wpcf-cocurrentloan'; var elem = jQuery(selector), c = 'd-none'; // Pour les radio, on vérifie la value sélectionnée var isChecked = jQuery('input[name="' + jQuery(this).attr('name') + '"]:checked').val() == '1'; elem.toggleClass(c, !isChecked); if (!isChecked) { jQuery(this).attr('name') === 'hascurrentloan' ? jQuery('.wpcf-currentloan--block').remove() : jQuery('.wpcf-cocurrentloan--block').remove(); } else if (elem.find(selector + '--block').length < 1) { jQuery(this).attr('name') === 'hascurrentloan' ? addCurrentLoan() : addCoCurrentLoan(); } }); // Affichage initial au chargement de la page function updateCurrentLoanVisibility() { var isChecked = jQuery('input[name="hascurrentloan"]:checked').val() == '1'; jQuery('.wpcf-currentloan').toggleClass('d-none', !isChecked); if (!isChecked) { jQuery('.wpcf-currentloan--block').remove(); } else if (jQuery('.wpcf-currentloan--block').length < 1) { addCurrentLoan(); } var isCoChecked = jQuery('input[name="cohascurrentloan"]:checked').val() == '1'; jQuery('.wpcf-cocurrentloan').toggleClass('d-none', !isCoChecked); if (!isCoChecked) { jQuery('.wpcf-cocurrentloan--block').remove(); } else if (jQuery('.wpcf-cocurrentloan--block').length < 1) { addCoCurrentLoan(); } } // Appel au chargement updateCurrentLoanVisibility(); jQuery('body').on('click', 'button.wpcf-currentload--add, button.wpcf-cocurrentload--add', function(e) { e.preventDefault() jQuery(this).hasClass('wpcf-currentload--add') ? addCurrentLoan() : addCoCurrentLoan() }) function addCurrentLoan() { var newLoan = currentLoanProto.replaceAll('__number__', currentLoanCount) jQuery(newLoan).insertBefore(jQuery('.wpcf-currentload--add')) currentLoanCount++ // Déclencher l'événement personnalisé pour la sauvegarde $(document).trigger('currentLoanAdded'); } function addCoCurrentLoan() { var newLoan = coCurrentLoanProto.replaceAll('__number__', coCurrentLoanCount) jQuery(newLoan).insertBefore(jQuery('.wpcf-cocurrentload--add')) coCurrentLoanCount++ // Déclencher l'événement personnalisé pour la sauvegarde $(document).trigger('currentLoanAdded'); } jQuery('body').on('click', '.wpcf-currentloan--block--remove, .wpcf-cocurrentloan--block--remove', function(e) { e.preventDefault(); jQuery(this).parent().remove(); // Déclencher l'événement personnalisé pour la sauvegarde $(document).trigger('currentLoanRemoved'); }); // Fonction pour afficher/cacher la zone isFichedDisplay function displayIsFichedZone() { var hasFiched = jQuery('input[name="isFiched"]:checked').length > 0 && jQuery('input[name="isFiched"]:checked').val() == 1; jQuery('.isFichedDisplay').toggleClass('d-none', !hasFiched); } // Vérifier l'état initial var isFichedField = jQuery('input[name="isFiched"]:checked'); if (isFichedField.length > 0) { displayIsFichedZone(); } // Ajouter l'écouteur d'événement pour le changement jQuery('body').on('change', 'input[name="isFiched"]', displayIsFichedZone); // Fonction pour afficher/cacher la zone isIndependant function displayIsIndependantZone() { var isIndependant = jQuery('select[name="job"]').val() === '5'; jQuery('.isIndependant').toggleClass('d-none', !isIndependant); } // Vérifier l'état initial var jobSelect = jQuery('select[name="job"]'); if (jobSelect.length > 0) { displayIsIndependantZone(); } // Ajouter l'écouteur d'événement pour le changement jQuery('body').on('change', 'select[name="job"]', displayIsIndependantZone); // Fonction pour afficher/cacher la zone hasLoyer function displayHasLoyerZone() { var isLocataire = jQuery('select[name="housing_status"]').val() === 'locataire'; jQuery('.hasLoyer').toggleClass('d-none', !isLocataire); } // Vérifier l'état initial var situationSelect = jQuery('select[name="housing_status"]'); if (situationSelect.length > 0) { displayHasLoyerZone(); } // Ajouter l'écouteur d'événement pour le changement jQuery('body').on('change', 'select[name="housing_status"]', displayHasLoyerZone); // Affichage de la taille totale des fichiers sélectionnés pour le champ IDpaycheck + vérification taille max 2 Mo jQuery('body').on('change', '#IDpaycheck, #IDcopaycheck', function(e) { var files = e.target.files; var totalSize = 0; var maxSize = 2 * 1024 * 1024; // 2 Mo en octets var errorFiles = []; for (var i = 0; i < files.length; i++) { totalSize += files[i].size; if (files[i].size > maxSize) { errorFiles.push(files[i].name); } } // Conversion en Mo, arrondi à 2 décimales var totalSizeMo = (totalSize / (1024 * 1024)).toFixed(2); // Création ou sélection de l'élément d'affichage var infoId = 'paycheck-size-info'; var infoElem = jQuery('#' + infoId); if (infoElem.length === 0) { infoElem = jQuery('
'); jQuery(this).after(infoElem); } // Création ou sélection de l'élément d'erreur var errorId = 'paycheck-size-error'; var errorElem = jQuery('#' + errorId); if (errorElem.length === 0) { errorElem = jQuery('
'); infoElem.after(errorElem); } var submitBtn = jQuery('.form-submit button[type="submit"]'); if (files.length > 0) { infoElem.text('Taille totale des fichiers sélectionnés : ' + totalSizeMo + ' Mo'); } else { infoElem.text(''); } if (errorFiles.length > 0) { errorElem.html('Fichier(s) trop volumineux (> 2 Mo) :
' + errorFiles.join(', ') + ''); submitBtn.prop('disabled', true); } else { errorElem.text(''); submitBtn.prop('disabled', false); } }); // Affichage conditionnel du champ "fonds propres" selon le choix Oui/Non avec effet slide const yesRadio = document.getElementById('estateequity_yes'); const noRadio = document.getElementById('estateequity_no'); if (yesRadio && noRadio) { $(yesRadio).on('change', function() { if (yesRadio.checked) { $('.fonds-propres').stop(true, true).slideDown(); $('[name="estateequity"]').prop('required', true); } }); $(noRadio).on('change', function() { if (noRadio.checked) { $('.fonds-propres').stop(true, true).slideUp(); $('[name="estateequity"]').prop('required', false); } }); // Initialisation à l'ouverture de la page if (yesRadio.checked) { $('.fonds-propres').show(); } else { $('.fonds-propres').hide(); } } // Variables pour le debouncing des inputs let capitalTimeout; let durationTimeout; let isCreditCalculationEnabled = true; if(jQuery('#creditCalcManualSwitch').length > 0) isCreditCalculationEnabled = true; // Liste des types de crédit qui utilisent des années const LOAN_TYPES_IN_YEARS = ['am', 'amr', 'cied', 'ph']; // Fonction pour mettre à jour l'affichage des valeurs calculées function updateCreditDisplay(result) { console.log('updateCreditDisplay - result:', result); // Calcul du TAEG const taeg = parseFloat(result.taux_nominal_annuel.replace('%', '')); const taux_debiteur = (taeg * 0.11 + taeg).toFixed(2); // Mise à jour des champs cachés jQuery('input[name="capital"]').val(result.capital); jQuery('input[name="duree"]').val(result.duree); jQuery('input[name="cout_total"]').val(result.cout_total); jQuery('input[name="mensualite"]').val(result.mensualite); jQuery('input[name="taux_nominal_annuel"]').val(result.taux_nominal_annuel); console.log('updateCreditDisplay - Champs cachés mis à jour:', { capital: result.capital, duree: result.duree, cout_total: result.cout_total, mensualite: result.mensualite, taux: result.taux_nominal_annuel }); // Mise à jour de l'affichage dans .credit-info jQuery('.credit-info .credit-item').each(function() { var $value = jQuery(this).find('.credit-value'); var $label = jQuery(this).find('.credit-label'); if($label.text().includes('Montant:')) { $value.text(result.capital + ' €'); } else if($label.text().includes('Durée:')) { $value.text(result.duree + ($('input[name="type_credit"]').val() === 'am' ? ' ans' : ' mois')); } else if($label.text().includes('Mensualité:')) { $value.text(result.mensualite + '€'); } else if($label.text().includes('Coût total:')) { $value.text(result.cout_total + '€'); } else if($label.text().includes('TAEG:')) { $value.text(taux_debiteur + '%'); } else if($label.text().includes('Taux débiteur:')) { $value.text(result.taux_nominal_annuel); } }); // Mise à jour du récapitulatif dans .info-credits-step jQuery('.info-credits-step .row > div').each(function() { $(this).find('p').each(function() { var $label = $(this); var $value = $label.next('span'); if($label.text().includes('Montant emprunté :')) { $value.text(result.capital + ' €'); } else if($label.text().includes('Durée :')) { $value.text(result.duree + ($('input[name="type_credit"]').val() === 'am' ? ' ans' : ' mois')); } else if($label.text().includes('Mensualité :')) { $value.text(result.mensualite + ' €'); } else if($label.text().includes('Coût Total :')) { $value.text(result.cout_total + ' €'); } else if($label.text().includes('Taux débiteur :')) { $value.text(result.taux_nominal_annuel); } else if($label.text().includes('Taeg :')) { $value.text(taux_debiteur + '%'); } }); }); console.log('updateCreditDisplay - Affichage mis à jour'); } // Fonction pour mettre à jour les limites et valeurs des champs selon le type de crédit function updateFieldLimits(selectedType) { const creditType = map_values[selectedType]; if (!creditType) { console.warn('Type de crédit non reconnu:', selectedType); return; } const $capitalField = jQuery('#IDbatiment_emprunt'); const $durationField = jQuery('#IDbatiment_duree'); let currentCapital = parseFloat($capitalField.val()); // Récupérer les limites depuis form_sliders pour les durées const sliderConfig = form_sliders[creditType]; if (!sliderConfig) { console.warn('Configuration slider non trouvée pour:', creditType); return; } // Récupérer les limites spécifiques du crédit depuis cd_js.groups const creditConfig = cd_js.groups[selectedType]; if (creditConfig && creditConfig.capital_max) { // Ne modifier le montant que s'il dépasse la limite du crédit choisi if (currentCapital > creditConfig.capital_max) { currentCapital = creditConfig.capital_selected || creditConfig.capital_max; $capitalField.val(currentCapital); } } // Mettre à jour les attributs min/max du champ capital $capitalField.attr('min', sliderConfig.capital_min); $capitalField.attr('max', sliderConfig.capital_max); $capitalField.attr('step', sliderConfig.capital_step || 100); // Ajuster la valeur du capital si nécessaire selon les limites du slider if (currentCapital > sliderConfig.capital_max) { currentCapital = sliderConfig.capital_max; $capitalField.val(currentCapital); } else if (currentCapital < sliderConfig.capital_min) { currentCapital = sliderConfig.capital_min; $capitalField.val(currentCapital); } // Déterminer la durée maximale disponible pour ce montant const isYearlyDuration = LOAN_TYPES_IN_YEARS.includes(creditType); let maxDurationForAmount = sliderConfig.durees[sliderConfig.durees.length - 1]; // Parcourir les durées disponibles dans l'ordre décroissant for (let i = sliderConfig.durees.length - 1; i >= 0; i--) { const duration = sliderConfig.durees[i]; // Vérifier si cette durée est valide pour le montant actuel if (currentCapital >= sliderConfig.capital_min && currentCapital <= sliderConfig.capital_max) { maxDurationForAmount = duration; break; } } // Convertir la durée maximale en années si nécessaire const displayDuration = isYearlyDuration ? Math.floor(maxDurationForAmount / 12) : maxDurationForAmount; const minDuration = isYearlyDuration ? Math.ceil(sliderConfig.duree_min / 12) : sliderConfig.duree_min; const maxDuration = isYearlyDuration ? Math.floor(sliderConfig.duree_max / 12) : sliderConfig.duree_max; // Mettre à jour les attributs min/max du champ durée $durationField.attr('min', minDuration); $durationField.attr('max', maxDuration); // Définir la durée maximale disponible $durationField.val(displayDuration); // Mettre à jour l'unité dans l'input-group-text jQuery('.input-group-text').text(isYearlyDuration ? 'Ans' : 'Mois'); // Mettre à jour l'affichage calculateCredit(); // Mettre à jour le champ caché type_credit jQuery('input[name="type_credit"]').val(creditType); } // Gestionnaire pour le changement de type de crédit jQuery('#IDestateloantype').on('change', function() { const creditType = jQuery(this).val(); if (creditType) { updateFieldLimits(creditType); calculateCredit(); } }); // Fonction pour arrondir le capital à la valeur acceptable la plus proche function snapCapitalValue(value, creditType) { if (!creditType || !form_sliders[creditType]) { return value; } const sliderConfig = form_sliders[creditType]; // Utiliser le step défini dans form_sliders const step = sliderConfig.capital_step || 100; // S'assurer que la valeur est dans les limites value = Math.max(sliderConfig.capital_min, Math.min(value, sliderConfig.capital_max)); // Arrondir à la valeur la plus proche selon le step return Math.round(value / step) * step; } // Fonction pour arrondir la durée à la valeur acceptable la plus proche function snapDurationValue(value, creditType) { if (!creditType || !form_sliders[creditType]) { return value; } const sliderConfig = form_sliders[creditType]; const isYearlyDuration = LOAN_TYPES_IN_YEARS.includes(creditType); console.log('snapDurationValue - value:', value); console.log('snapDurationValue - creditType:', creditType); console.log('snapDurationValue - isYearlyDuration:', isYearlyDuration); if (isYearlyDuration) { // Pour les crédits en années, on vérifie juste les limites min/max // Les limites sont déjà en années pour ces types de crédit const minYears = sliderConfig.duree_min; const maxYears = sliderConfig.duree_max; console.log('snapDurationValue - minYears:', minYears); console.log('snapDurationValue - maxYears:', maxYears); if (value > maxYears) return maxYears; if (value < minYears) return minYears; return value; } else { // Pour les autres crédits, on cherche la durée disponible la plus proche let closestDuration = sliderConfig.durees[0]; let minDiff = Math.abs(value - sliderConfig.durees[0]); for (let i = 1; i < sliderConfig.durees.length; i++) { const diff = Math.abs(value - sliderConfig.durees[i]); if (diff < minDiff) { minDiff = diff; closestDuration = sliderConfig.durees[i]; } } return closestDuration; } } // Fonction pour calculer la mensualité function calc_mens(mnt, dur, taeg) { const creditType = jQuery('input[name="type_credit"]').val(); // Pour le crédit pont, on ne calcule que les intérêts mensuels if (creditType === 'cdp') { const t100 = taeg / 100; // Calcul des intérêts mensuels uniquement : (capital * taux_annuel) / 12 const mens = (mnt * t100) / 12; return Math.floor(mens * 100) / 100; } // Pour les autres types de crédit, calcul normal const t100 = taeg / 100; const t101 = 1 + t100; const d12 = 1 / 12; const dur12 = dur / 12; const buf1 = Math.pow(t101, d12); const buf2 = Math.pow(1 / t101, dur12); const mens = (mnt * (buf1 - 1)) / (1 - buf2); return Math.floor(mens * 100) / 100; } // Notre propre fonction de calcul qui utilise les fonctions de base de cd_main.js function calculateCredit() { const capital = parseFloat(jQuery('#IDbatiment_emprunt').val()); const duration = parseInt(jQuery('#IDbatiment_duree').val()); const selectedType = jQuery('#IDestateloantype').val(); const creditType = map_values[selectedType]; console.log('=== Début calcul crédit ==='); console.log('Type sélectionné:', selectedType); console.log('Type de crédit mappé:', creditType); console.log('Capital:', capital); console.log('Durée:', duration); if (isNaN(capital) || isNaN(duration) || !creditType) { console.warn('Valeurs invalides pour le calcul:', { capital: isNaN(capital) ? 'invalide' : capital, duration: isNaN(duration) ? 'invalide' : duration, selectedType, creditType: !creditType ? 'non mappé' : creditType }); return; } // Convertir la durée en mois si nécessaire pour les calculs const isYearlyDuration = LOAN_TYPES_IN_YEARS.includes(creditType); const durationForCalculation = isYearlyDuration ? duration * 12 : duration; console.log('Durée pour calcul:', durationForCalculation, isYearlyDuration ? '(en années)' : '(en mois)'); let results; // Appeler la fonction de calcul appropriée switch(creditType) { case 'pat': results = calculate_pat(capital, duration); break; case 'am': results = calculate_am(capital, duration); break; case 'ph': results = calculate_ph(capital, duration); break; case 'amr': case 'cdp': case 'cied': results = calculate_mono_rate_bt_10_30(creditType, capital, duration); break; case 'fin_neuve': results = calculate_fin_neuve(capital, duration); break; case 'fin_occ_m3a': results = calculate_pao_m_3(capital, duration); break; case 'fin_occ_p3a': results = calculate_pao_p_3(capital, duration); break; case 'mobil_carav': results = calculate_mobilhome(capital, duration); break; case 'regroup_cred': results = calculate_regroupement_de_credit(capital, duration); break; case 'frais_notaire': results = calculate_frais_notaire(capital, duration); break; case 'but_immo': results = calculate_but_immo(capital, duration); break; default: console.warn('Type de crédit non reconnu:', creditType); results = calculate_pat(capital, duration); } if (!results) { console.warn('Pas de résultats retournés par la fonction de calcul'); return; } console.log('Résultats bruts:', results); // Extraire les valeurs des résultats const [min_duration, max_duration, selected_duration, duree_in_range, taux_nominal] = results; // Convertir le taux en nombre si c'est une chaîne const taux = parseFloat(String(taux_nominal).replace('%', '')); if (isNaN(taux)) { console.error('Taux nominal invalide après conversion:', { original: taux_nominal, converti: taux }); return; } console.log('Taux nominal (débiteur):', taux + '%'); console.log('Durée sélectionnée:', selected_duration, isYearlyDuration ? 'mois (convertie)' : 'mois'); // Pour les crédits en années, la durée retournée est déjà en mois const durationInMonths = isYearlyDuration ? durationForCalculation : selected_duration; // Calculer la mensualité avec notre fonction locale const mensualite = calc_mens(capital, durationInMonths, taux); if (isNaN(mensualite)) { console.error('Erreur dans le calcul de la mensualité:', { capital, durationInMonths, taux }); return; } const cout_total = (mensualite * durationInMonths).toFixed(2); console.log('Mensualité calculée:', mensualite.toFixed(2) + '€'); console.log('Coût total calculé:', cout_total + '€'); // Créer l'objet result pour updateCreditDisplay const displayResult = { capital: capital, duree: duration, mensualite: mensualite.toFixed(2), cout_total: cout_total, taux_nominal_annuel: taux.toFixed(2) + ' %', taux_debiteur: taux.toFixed(2) + ' %' }; console.log('Résultat final pour affichage:', displayResult); console.log('--- Fin calcul crédit ---'); // Mettre à jour l'affichage updateCreditDisplay(displayResult); // Mettre à jour le champ caché type_credit jQuery('input[name="type_credit"]').val(creditType); } if(isCreditCalculationEnabled) { // Remplacer tous les appels à calculate_mensualite par calculateCredit jQuery('#IDbatiment_emprunt') .on('input', function() { const $this = jQuery(this); clearTimeout(capitalTimeout); if ($this.val() !== '') { capitalTimeout = setTimeout(() => { let capital = parseFloat($this.val()); if (!isNaN(capital)) { const creditType = jQuery('input[name="type_credit"]').val(); const snappedValue = snapCapitalValue(capital, creditType); if (snappedValue !== capital) { $this.val(snappedValue); } calculateCredit(); } }, 500); // Délai un peu plus long pour laisser le temps de taper } }) .on('change', function() { // Validation finale quand on quitte le champ (onblur) const $this = jQuery(this); if ($this.val() !== '') { let capital = parseFloat($this.val()); if (!isNaN(capital)) { const selectedType = jQuery('#IDestateloantype').val(); const creditConfig = cd_js.groups[selectedType]; // Vérifier les limites du crédit if (creditConfig && creditConfig.capital_max && capital > creditConfig.capital_max) { capital = creditConfig.capital_selected || creditConfig.capital_max; $this.val(capital); } // Snap à la valeur la plus proche et recalcul const creditType = jQuery('input[name="type_credit"]').val(); const snappedValue = snapCapitalValue(capital, creditType); if (snappedValue !== capital) { $this.val(snappedValue); } calculateCredit(); } } }); jQuery('#IDbatiment_duree') .on('input', function() { const $this = jQuery(this); clearTimeout(durationTimeout); durationTimeout = setTimeout(() => { let duration = parseInt($this.val()); if (!isNaN(duration)) { const creditType = jQuery('input[name="type_credit"]').val(); console.log('Input duration:', duration); console.log('Credit type:', creditType); console.log('Is yearly:', LOAN_TYPES_IN_YEARS.includes(creditType)); // Ne pas snapper pendant la saisie pour les crédits en années if (LOAN_TYPES_IN_YEARS.includes(creditType)) { calculateCredit(); } else { const snappedValue = snapDurationValue(duration, creditType); if (snappedValue !== duration) { $this.val(snappedValue); } calculateCredit(); } } }, 300); }) .on('change', function() { let duration = parseInt($(this).val()); if (!isNaN(duration)) { const creditType = jQuery('input[name="type_credit"]').val(); console.log('onchange - creditType:', creditType); console.log('onchange - LOAN_TYPES_IN_YEARS:', LOAN_TYPES_IN_YEARS); console.log('onchange - includes?', LOAN_TYPES_IN_YEARS.includes(creditType)); if (LOAN_TYPES_IN_YEARS.includes(creditType)) { console.log('change', duration); const snappedValue = snapDurationValue(duration, creditType); console.log('snappedValue', snappedValue); if (snappedValue !== duration) { $(this).val(snappedValue); } } else { // Pour les autres crédits, comportement normal const snappedValue = snapDurationValue(duration, creditType); if (snappedValue !== duration) { $(this).val(snappedValue); } } calculateCredit(); } }); // Calcul initial au chargement /* $(document).ready(function() { const creditType = $('input[name="type_credit"]').val(); if (creditType) { updateFieldLimits(creditType); calculateCredit(); } }); */ // Gestionnaire pour la soumission du formulaire jQuery('.form-submit').on('submit', function() { // Recalculer une dernière fois avant l'envoi calculateCredit(); }); } function findRate(type) { console.log('findRate - Type recherché:', type); let rate; switch(type) { case 'amr': rate = cd_js.groups.achat_maison_de_rapport.de_10_a_30_ans; break; case 'cdp': rate = cd_js.groups.credit_pont.de_10_a_30_ans; break; case 'cied': rate = cd_js.groups.independants_et_entreprises_en_difficultes.de_10_a_30_ans; break; default: console.warn('Type de crédit non reconnu pour le taux mono:', type); rate = null; } console.log('findRate - Taux trouvé:', rate); return rate; } function calculate_mono_rate_bt_10_30(type, selected_capital, selected_duration) { const annual_rate = findRate(type); if (!annual_rate) { console.error('Taux non trouvé pour le type:', type); return null; } const min_duration = 10; const max_duration = 30; let duree_in_range = true; const add_message = ''; if (min_duration <= parseInt(selected_duration) && parseInt(selected_duration) <= max_duration) { // La durée est dans la plage } else { duree_in_range = false; selected_duration = max_duration; } // Convertir la durée en mois pour le calcul selected_duration = selected_duration * 12; return [min_duration, max_duration, selected_duration, duree_in_range, annual_rate, add_message]; } });