PageSourceSearch

https://www.biomotors.pro/wp-content/plugins/biomotors-sales-assistant/assets/js/app.js?ver=1790903282

js biomotors.pro collected 2026-10-02 01:08:58 UTC 38,260 bytes, 985 lines download raw bytes

1let currentLeadId = null;
2
3/**
4 * Triggered when a table row is clicked
5 */
6function selectLead(id, row) {
7    currentLeadId = id;
8
9    // 1. Highlight the selected row
10    document.querySelectorAll('tbody tr').forEach(r => r.classList.remove('selected'));
11    row.classList.add('selected');
12
13    // 2. Extract data from the clicked row (cells index matches your table structure)
14    const name = row.cells[1].innerText;
15    const plate = row.cells[3].innerText;
16    const vehicle = row.cells[4].innerText + ' ' + row.cells[5].innerText;
17    const phone = row.getAttribute('data-phone'); // Get phone from data attribute
18    const initials = name.split(' ').map(n => n[0]).join('').substring(0, 2).toUpperCase();
19
20    // 3. Update the Client Banner content
21    document.getElementById('bannerName').innerText = name;
22    document.getElementById('bannerInitials').innerText = initials;
23    document.getElementById('bannerVehicle').innerText = vehicle;
24    document.getElementById('bannerPlate').innerText = plate;
25    document.getElementById('bannerPhone').innerText = phone;
26
27// DYNAMIC URL LOGIC
28    const banner = document.getElementById('clientBanner');
29    const eligibilityBtn = document.getElementById('eligibilityBtn');
30
31// Simply grab the dynamic URL from the data attribute
32    const baseUrl = banner.getAttribute('data-base-url');
33
34// Apply it to the button (no extra parameters)
35    if (eligibilityBtn) {
36        eligibilityBtn.href = baseUrl;
37    }
38    // 4. Show the Banner and Call Status sections
39    document.getElementById('clientBanner').classList.add('visible');
40    document.getElementById('callStatusSection').classList.add('visible');
41
42    // 5. Smooth scroll to the Banner
43    document.getElementById('clientBanner').scrollIntoView({
44        behavior: 'smooth',
45        block: 'start'
46    });
47
48}
49
50/**
51 * Handle "Oui/Non" buttons
52 */
53
54
55function updateLeadStatus(leadId, value) {
56    // We proceed silently now for a better user experience
57    jQuery.ajax({
58        type: "POST",
59        url: "/wp-content/themes/biomotors-child/core/ajax/valideContactLead.php",
60        data: {
61            lead_id: leadId,
62            status_value: value
63        },
64        success: function(response) {
65            // UI Update: Find the row and update the badge visually
66            const row = document.querySelector(`tr[onclick*="selectLead(${leadId}"]`);
67            if (row) {
68                const badgeCell = row.cells[7]; // Statut is the 8th column
69                if (value === 1) {
70                    badgeCell.innerHTML = '<span class="badge-contacted">Contacté</span>';
71                    // Success alert removed for smoother UX
72                } else {
73                    badgeCell.innerHTML = '<span class="badge-pending">En attente</span>';
74                    // Success alert removed for smoother UX
75                }
76            }
77        },
78        error: function() {
79            // Swapped to console.error so it doesn't interrupt the technician
80            console.error("Erreur lors de la mise à jour du statut pour le lead ID: " + leadId);
81        }
82    });
83}
84
85/**
86 * Logic for the "Suivant" button in the banner
87 */
88function nextLead() {
89    const rows = Array.from(document.querySelectorAll('tbody tr'));
90    const currentIndex = rows.findIndex(r => r.classList.contains('selected'));
91    const nextIndex = (currentIndex + 1) % rows.length;
92
93    document.querySelectorAll('.step').forEach(s => s.classList.remove('active'));
94
95    document.getElementById('guideSection').style.display = 'none';
96    document.getElementById('miniStats').style.display = 'none';
97    document.getElementById('voicemailSection').style.display = 'none';
98    document.getElementById('voicemailRecallSection').style.display = 'none';
99    document.getElementById('callResultSection').style.display = 'none';
100
101    // Remove "active" class from the Yes/No call buttons
102    document.querySelectorAll('.call-btn').forEach(btn => btn.classList.remove('active'));
103
104    // Reset Stepper UI to step 1 (visually)
105    updateStepperUI(1);
106
107    // Simulate a click on the next row
108    rows[nextIndex].click();
109}
110/**
111 * Toggles accordion steps
112 */
113
114function toggleStep(header) {
115    const stepElement = header.parentElement;
116    const isOpening = !stepElement.classList.contains('active');
117
118    // 1. Toggle ONLY this step (others remain as they were)
119    stepElement.classList.toggle('active');
120
121    // 2. If we are opening the step, update the stepper and scroll to it
122    if (isOpening) {
123        const stepNumber = parseInt(stepElement.id.replace('step-', ''));
124        updateStepperUI(stepNumber);
125
126        // 3. Precision Scroll Logic
127        // We calculate the position manually to avoid the "middle of the page" issue
128        const headerHeight = 170; // Total height of your sticky banner + stepper
129        const elementPosition = stepElement.getBoundingClientRect().top;
130        const offsetPosition = elementPosition + window.pageYOffset - headerHeight;
131
132        window.scrollTo({
133            top: offsetPosition,
134            behavior: 'smooth'
135        });
136    }
137}
138
139
140function setCallStatus(status, event) { // <-- Added 'event' parameter
141    if (!currentLeadId) return;
142
143    // Fix: Remove 'active' from all, then add to the one clicked
144    document.querySelectorAll('.call-btn').forEach(btn => btn.classList.remove('active'));
145
146    // Safety check for the event
147    if (event && event.currentTarget) {
148        event.currentTarget.classList.add('active');
149    }
150
151    // 2. UI Elements
152    const voicemailSec = document.getElementById('voicemailSection');
153    const voicemailRecall = document.getElementById('voicemailRecallSection');
154    const statsSec = document.getElementById('miniStats');
155    const guideSec = document.getElementById('guideSection');
156    const resultSec = document.getElementById('callResultSection');
157
158    // 1. Update Database Status (Moved slightly down for logic flow)
159    let metaValue = (status === 'picked') ? 1 : 2;
160    updateLeadStatus(currentLeadId, metaValue);
161
162    // 3. Logic based on status
163    if (status === 'picked') {
164        // HIDE Voicemail, SHOW Guide
165        voicemailSec.style.display = 'none';
166        voicemailRecall.style.display = 'none';
167        statsSec.style.display = 'block';
168        guideSec.style.display = 'block';
169
170        // THIS IS NOW ACCESSIBLE because the code didn't crash above!
171        resultSec.style.display = 'block';
172
173        // Reset Stepper to Step 1
174        updateStepperUI(1);
175
176        // Reset Accordions: Ensure ONLY Step 1 is open
177        document.querySelectorAll('.step').forEach(s => s.classList.remove('active'));
178        document.getElementById('step-1').classList.add('active');
179
180        // INJECT DATA into script
181        const name = document.getElementById('bannerName').innerText;
182        const vehicle = document.getElementById('bannerVehicle').innerText;
183        const plate = document.getElementById('bannerPlate').innerText;
184
185        document.querySelectorAll('.dyn-client-name').forEach(el => el.innerText = name);
186        document.querySelectorAll('.dyn-client-vehicle').forEach(el => el.innerText = vehicle);
187        document.querySelectorAll('.dyn-client-plate').forEach(el => el.innerText = plate);
188
189        // Scroll to the start of the assistant
190        statsSec.scrollIntoView({ behavior: 'smooth' });
191
192    } else if (status === 'voicemail') {
193        // SHOW Voicemail, HIDE Guide
194        statsSec.style.display = 'none';
195        guideSec.style.display = 'none';
196        resultSec.style.display = 'none';
197        voicemailSec.style.display = 'block';
198        voicemailRecall.style.display = 'block';
199
200        const name = document.getElementById('bannerName').innerText;
201        const scriptName = document.querySelector('.banner-name-script');
202        if (scriptName) scriptName.innerText = name;
203
204        voicemailSec.scrollIntoView({ behavior: 'smooth' });
205    }
206
207    /* const bBar = document.getElementById('bottomBar');
208     if (status === 'picked') {
209         // Show the bottom bar for live calls
210         if (bBar) bBar.classList.add('visible');
211     } else {
212         // HIDE the bottom bar for voicemails to force the user to save the recall
213         if (bBar) bBar.classList.remove('visible');
214     }*/
215}
216
217/**
218 * Copy Plate Function
219 */
220function copyPlate() {
221    const plate = document.querySelector('.dyn-client-plate').innerText;
222    navigator.clipboard.writeText(plate);
223    const btn = document.getElementById('copyPlateBtn');
224    btn.innerText = "✅ Copié";
225    setTimeout(() => { btn.innerText = "📋 Copier l'immatriculation"; }, 2000);
226}
227
228// Helper to handle pill selection
229// Variables to hold selections until "Enregistrer" is clicked
230let selectedRecallDate = "";
231let selectedRecallSlot = "";
232
233/**
234 * Handles Date Selection (J+n or Date Picker)
235 */
236
237function selectRecallDate(type, btn) {
238    let targetDate = new Date();
239
240    if (type === 'J+1') targetDate.setDate(targetDate.getDate() + 1);
241    else if (type === 'J+3') targetDate.setDate(targetDate.getDate() + 3);
242    else if (type === 'J+7') targetDate.setDate(targetDate.getDate() + 7);
243
244    // FIX: Format to YYYY-MM-DD using local time (No UTC shift)
245    const year = targetDate.getFullYear();
246    const month = String(targetDate.getMonth() + 1).padStart(2, '0');
247    const day = String(targetDate.getDate()).padStart(2, '0');
248    selectedRecallDate = `${year}-${month}-${day}`;
249
250    document.querySelectorAll('#voicemailRecallSection .rp-pill').forEach(b => b.classList.remove('selected'));
251    btn.classList.add('selected');
252    document.getElementById('vmCustomDate').value = "";
253}
254/**
255 * Handles Time Slot Selection
256 */
257function selectRecallSlot(val, btn) {
258    selectedRecallSlot = val; // Matin, Midi, Apres-Midi, Soir, Indifférent
259
260    // UI: Update Pills
261    document.querySelectorAll('#voicemailRecallSection .rp-pill-green').forEach(b => b.classList.remove('selected'));
262    btn.classList.add('selected');
263}
264
265
266/**
267 * Saves everything to Database on click
268 */
269function saveRecallAndNext() {
270    if (!currentLeadId) return;
271
272    // Check if user used the date picker instead of J+ pills
273    const customDate = document.getElementById('vmCustomDate').value;
274    const finalDate = customDate ? customDate : selectedRecallDate;
275
276    if (!finalDate || !selectedRecallSlot) {
277        alert("⚠️ Choisissez au minimum une échéance (J+1, J+3, J+7 ou une date).");
278        return;
279    }
280
281    jQuery.ajax({
282        type: "POST",
283        url: "/wp-content/themes/biomotors-child/core/ajax/valideContactLead.php",
284        data: {
285            lead_id: currentLeadId,
286            status_value: 2, // En attente
287            lead_date_rappel: finalDate,
288            lead_creneau_rappel: selectedRecallSlot
289        },
290        success: function() {
291            // --- date rappel UI UPDATE BLOCK ---
292            const row = document.querySelector(`tr[onclick*="selectLead(${currentLeadId}"]`);
293            if (row) {
294                const badgeCell = row.cells[7]; // This is the 'Statut' column
295
296                // Format the date from YYYY-MM-DD to DD/MM
297                const dateParts = finalDate.split('-');
298                const formattedDate = dateParts[2] + '/' + dateParts[1];
299
300                // Update the table cell immediately
301                badgeCell.innerHTML = `<span class="badge-pending">📅 Rappel (${formattedDate})</span>`;
302            }
303            // --- date rappel UI UPDATE BLOCK ---
304            // 1. Hide the form
305            document.getElementById('vmRpForm').style.display = 'none';
306
307            // 2. Set details in the confirmation screen
308            document.getElementById('vmRpConfDetail').textContent = finalDate + ' · ' + selectedRecallSlot;
309
310            // 3. Show the confirmation screen
311            document.getElementById('vmRpConfirmation').style.display = 'block';
312
313            // 4. Wait 2 seconds, then go to next lead
314            setTimeout(() => {
315                // Reset for next lead
316                document.getElementById('vmRpForm').style.display = 'block';
317                document.getElementById('vmRpConfirmation').style.display = 'none';
318
319                // SAFETY CHECK: Only clear the note if the element actually exists
320                const noteField = document.getElementById('vmRpNote');
321                if (noteField) noteField.value = "";
322
323                // MANDATORY: Clear the "Répondeur" button active state
324                // This ensures the next lead starts with no buttons highlighted
325                document.querySelectorAll('.call-btn').forEach(btn => btn.classList.remove('active'));
326                nextLead();
327            }, 2000);
328        }
329    });
330}
331
332/**
333 * calc economy
334 */
335
336let calcMode = 'pleins';
337let currentSavingsMonth = 67;
338let currentSavingsYear = 804;
339
340function switchCalcMode(mode, btn) {
341    calcMode = mode;
342    document.querySelectorAll('.tabs .tab-btn').forEach(b => b.classList.remove('active'));
343    btn.classList.add('active');
344    document.getElementById('calcPleins').style.display = mode === 'pleins' ? 'block' : 'none';
345    document.getElementById('calcKm').style.display = mode === 'km' ? 'block' : 'none';
346    updateCalc();
347}
348
349
350function updateCalc() {
351    // Basic assumptions used in the template
352    const conso = 7.5;
353    const reservoir = parseFloat(document.getElementById('tankSize').value) || 50;
354    const surcon = 1.15;
355    // const pSP = 1.70;
356    // const pE85 = 0.75;
357    const pSP = parseFloat(bmSettings.priceSP95) || 1.70;
358    const pE85 = parseFloat(bmSettings.priceE85) || 0.75;
359
360    const msE85 = document.getElementById('msE85');
361    if (msE85) {
362        msE85.textContent = pE85.toFixed(2).replace('.', ',') + ' €/L';
363    }
364
365
366    let lpm; // Liters per month
367
368    if (calcMode === 'pleins') {
369        const p = parseFloat(document.getElementById('pleinsRange').value);
370        const pLabel = p % 1 === 0 ? p.toFixed(0) : p.toFixed(1).replace('.', ',');
371        document.getElementById('pleinsBig').textContent = pLabel + (p > 1 ? ' pleins / mois' : ' plein / mois');
372        lpm = p * reservoir;
373    } else {
374        const km = parseInt(document.getElementById('kmRange').value);
375        document.getElementById('kmVal').textContent = km.toLocaleString('fr-FR');
376        lpm = (km / 12 / 100) * conso;
377    }
378
379    // Calculation: Savings = Cost SP95 - Cost E85 (with 15% overconsumption)
380    currentSavingsMonth = Math.round(lpm * pSP - (lpm * surcon * pE85));
381    currentSavingsYear = currentSavingsMonth * 12;
382
383    // Update Step 3 Display
384    document.getElementById('savMonth').textContent = currentSavingsMonth + ' €';
385    document.getElementById('savYear').textContent = currentSavingsYear.toLocaleString('fr-FR') + ' €';
386    document.getElementById('sav5Y').textContent = (currentSavingsYear * 5).toLocaleString('fr-FR') + ' €';
387
388    // Update Dynamic text in the scripts
389    document.querySelectorAll('.dynSM').forEach(e => e.textContent = currentSavingsMonth + ' €');
390    document.querySelectorAll('.dynSY').forEach(e => e.textContent = currentSavingsYear.toLocaleString('fr-FR') + ' €');
391
392    // Update Mini-Stats Bar at the top
393    const msEco = document.getElementById('msEco');
394    if(msEco) msEco.textContent = currentSavingsMonth + ' €/mois';
395
396    updateMargin();
397
398
399    //If it's a Leclerc account, update Step 4 and Step 5 boxes
400    if (typeof updateLeclercPriceDisplay === 'function') {
401        updateLeclercPriceDisplay();
402    }
403
404    //Update the Financing timeline specifically
405    if (typeof updateFinancingDisplay === 'function') {
406        updateFinancingDisplay();
407    }
408}
409
410// Initialize calc on load
411document.addEventListener('DOMContentLoaded', () => {
412    // 1. If the calculator exists, run the fuel savings math (Step 3)
413    if(document.getElementById('pleinsRange')) {
414        updateCalc();
415    }
416
417    // 2. THE FIX: Initialize the Step 4 Pricing
418    // This forces the script to calculate the 25% discount immediately on load
419    updatePrices();
420    updateMargin();
421});
422
423
424/**
425 * NEW HELPER: This creates the 25% and 40% prices on the fly
426 * using the prices you set in the WordPress Back Office.
427 */
428function getDynamicPricing() {
429    const baseConnect = parseFloat(bmSettings.priceConnect) || 599.17;
430    const baseAir     = parseFloat(bmSettings.priceAir) || 798.17;
431
432    return {
433        connect: {
434            pro: baseConnect,
435            r25: baseConnect * 0.75, // calculates -25%
436            r40: baseConnect * 0.60  // calculates -40%
437        },
438        air: {
439            pro: baseAir,
440            r25: baseAir * 0.75,
441            r40: baseAir * 0.60
442        }
443    };
444}
445
446let currentRemise = 25;
447let currentProduct = 'connect';
448let currentPTTC = 959;
449let currentROI = 14;
450
451function switchRemise(val) {
452    currentRemise = val;
453    document.getElementById('r25btn').classList.toggle('active', val === 25);
454    document.getElementById('r40btn').classList.toggle('active', val === 40);
455    document.getElementById('r40note').style.display = (val === 40) ? 'block' : 'none';
456    updatePrices();
457    updateMargin();
458}
459
460function selectProduct(card, type) {
461    currentProduct = type;
462    document.querySelectorAll('.product-card').forEach(c => c.classList.remove('selected'));
463    card.classList.add('selected');
464    updateMargin();
465}
466
467function updatePrices() {
468
469    const prConnect = document.getElementById('prConnect');
470    if (!prConnect) return;
471
472    const prices = getDynamicPricing(); // Call the dynamic function
473    const k = (currentRemise === 40) ? 'r40' : 'r25';
474
475    document.getElementById('prConnect').textContent = prices.connect[k].toFixed(2).replace('.', ',') + ' € HT';
476    document.getElementById('prAir').textContent = prices.air[k].toFixed(2).replace('.', ',') + ' € HT';
477}
478
479function updateMargin() {
480    const inputPose = document.getElementById('inputPose');
481    if (!inputPose) return;
482
483    const prices = getDynamicPricing(); // Call the dynamic function
484    const k = (currentRemise === 40) ? 'r40' : 'r25';
485
486    const pa = prices[currentProduct][k];
487    const pose = parseFloat(document.getElementById('inputPose').value) || 0;
488    const marge = parseFloat(document.getElementById('margeRange').value) || 0;
489
490    document.getElementById('margeVal').textContent = marge + ' € HT';
491    document.getElementById('dispPA').textContent = pa.toFixed(2).replace('.', ',') + ' €';
492
493    const totalHT = pa + pose + marge;
494    currentPTTC = Math.round(totalHT * 1.2); // +20% TVA
495
496    // ROI calculation: (Total Cost / Monthly Savings from Step 3)
497    currentROI = (currentSavingsMonth > 0) ? Math.ceil(currentPTTC / currentSavingsMonth) : 0;
498
499    document.getElementById('mCost').textContent = Math.round(pa + pose) + ' €';
500    document.getElementById('mPrice').textContent = currentPTTC.toLocaleStr
500ing('fr-FR') + ' €';
501    document.getElementById('mROI').textContent = currentROI + ' mois';
502
503    // Update the visual scripts for the technician
504    document.querySelectorAll('.dynPTTC').forEach(e => e.textContent = currentPTTC.toLocaleString('fr-FR') + ' €');
505    document.querySelectorAll('.dynROI').forEach(e => e.textContent = currentROI + ' mois');
506}
507// --- NEW GLOBALS FOR CALL RESULT SECTION ONLY ---
508let selectedResultDateDirect = "";
509let selectedResultSlotDirect = "";
510let selectedCallResultValue = "";
511
512/**
513 * Handles the selection of the main call result buttons
514 */
515function setCallResult(result, btn) {
516    selectedCallResultValue = result; // 'intéressé', 'à recontacter', 'pas intéressé'
517
518    // UI: Update button visual state
519    document.querySelectorAll('#callResultSection .result-btn').forEach(b => b.classList.remove('chosen'));
520    btn.classList.add('chosen');
521
522    const planner = document.getElementById('recallPlanner');
523
524    if (result === 'à recontacter') {
525        // Show planner for "À recontacter"
526        planner.style.display = 'block';
527        planner.scrollIntoView({ behavior: 'smooth', block: 'center' });
528    } else {
529        // Hide planner and save immediately for "intéressé" or "pas intéressé"
530        planner.style.display = 'none';
531        saveResultAndNext();
532    }
533}
534
535/**
536 * Handles Date Selection for the NEW Result Planner
537 */
538
539function selectRecallDateDirect(type, btn) {
540    let targetDate = new Date();
541
542    if (type === 'J+1') targetDate.setDate(targetDate.getDate() + 1);
543    else if (type === 'J+3') targetDate.setDate(targetDate.getDate() + 3);
544    else if (type === 'J+7') targetDate.setDate(targetDate.getDate() + 7);
545
546    // FIX: Format to YYYY-MM-DD using local time
547    const year = targetDate.getFullYear();
548    const month = String(targetDate.getMonth() + 1).padStart(2, '0');
549    const day = String(targetDate.getDate()).padStart(2, '0');
550    selectedResultDateDirect = `${year}-${month}-${day}`;
551
552    document.querySelectorAll('#callResultSection .rp-pill').forEach(b => b.classList.remove('selected'));
553    btn.classList.add('selected');
554    document.getElementById('rpCustomDate').value = "";
555}
556
557/**
558 * Handles Slot Selection for the NEW Result Planner
559 */
560function selectRecallSlotDirect(val, btn) {
561    selectedResultSlotDirect = val;
562    document.querySelectorAll('#callResultSection .rp-pill-green').forEach(b => b.classList.remove('selected'));
563    btn.classList.add('selected');
564}
565
566/**
567 * Logic to save the result and move to next lead
568 */
569function saveResultAndNext() {
570    if (!currentLeadId) return;
571
572    let leadStatus;
573    let finalDate = "";
574    let finalSlot = "";
575    let finalNote = document.getElementById('rpNote').value;
576
577    // Mapping: intéressé/pas intéressé -> Status 1 | à recontacter -> Status 2
578    if (selectedCallResultValue === 'à recontacter') {
579        leadStatus = 2;
580        const customDate = document.getElementById('rpCustomDate').value;
581        finalDate = customDate ? customDate : selectedResultDateDirect;
582        finalSlot = selectedResultSlotDirect;
583
584        if (!finalDate || !finalSlot) {
585            alert("⚠️ Veuillez sélectionner une échéance et un créneau.");
586            return;
587        }
588    } else {
589        leadStatus = 1;
590    }
591
592    jQuery.ajax({
593        type: "POST",
594        url: "/wp-content/themes/biomotors-child/core/ajax/valideContactLead.php",
595        data: {
596            lead_id: currentLeadId,
597            status_value: leadStatus, // lead_contacte
598            lead_resultat_appel: selectedCallResultValue, // New Meta Key
599            lead_date_rappel: finalDate,
600            lead_creneau_rappel: finalSlot,
601            lead_note_rappel: finalNote // New Meta Key
602        },
603        success: function() {
604
605            const row = document.querySelector(`tr[onclick*="selectLead(${currentLeadId}"]`);
606            if (row) {
607                const badgeCell = row.cells[7]; // The Status column
608                // if (leadStatus === 2) {
609                //     // Format the date for the badge (YYYY-MM-DD to DD/MM)
610                //     // const dateParts = finalDate.split('-');
611                //     // const formattedDate = dateParts[2] + '/' + dateParts[1];
612                //     // badgeCell.innerHTML = `<span class="badge-pending">En attente (${formattedDate})</span>`;
613                //     badgeCell.innerHTML = '<span class="badge-pending">En attente</span>';
614                // } else {
615                //     badgeCell.innerHTML = '<span class="badge-contacted">Contacté</span>';
616                // }
617                if (leadStatus === 2) {
618                    const dateParts = finalDate.split('-');
619                    const formattedDate = dateParts[2] + '/' + dateParts[1];
620                    badgeCell.innerHTML = `<span class="badge-pending">📅 À recontacter (${formattedDate})</span>`;
621                } else if (selectedCallResultValue === 'intéressé') {
622                    badgeCell.innerHTML = '<span class="badge-contacted">✅ Intéressé</span>';
623                } else if (selectedCallResultValue === 'pas intéressé') {
624                    badgeCell.innerHTML = '<span class="badge-new" style="background:#ffebee; color:#c62828; border:1px solid #ef5350;">Pas intéressé</span>';
625                } else {
626                    badgeCell.innerHTML = '<span class="badge-contacted">Contacté</span>';
627                }
628
629                const noteInput = row.cells[8].querySelector('.lead-comment-input');
630                if (noteInput) {
631                    noteInput.value = finalNote;
632                }
633            }
634
635            if (leadStatus === 2) {
636                // Show confirmation animation
637                document.getElementById('rpForm').style.display = 'none';
638                document.getElementById('rpConfDetail').textContent = finalDate + ' · ' + finalSlot;
639                document.getElementById('rpConfirmation').style.display = 'block';
640
641                setTimeout(() => {
642                    resetResultSection();
643                    nextLead();
644                }, 2000);
645            } else {
646                resetResultSection();
647                nextLead();
648            }
649        }
650    });
651}
652
653function resetResultSection() {
654    document.getElementById('callResultSection').style.display = 'none';
655    document.getElementById('recallPlanner').style.display = 'none';
656    document.getElementById('rpForm').style.display = 'block';
657    document.getElementById('rpConfirmation').style.display = 'none';
658    document.getElementById('rpNote').value = "";
659    selectedCallResultValue = "";
660    selectedResultDateDirect = "";
661    selectedResultSlotDirect = "";
662}
663
664/**
665 * Updates the horizontal stepper UI based on the active step number
666 */
667function updateStepperUI(activeStep) {
668    for (let i = 1; i <= 7; i++) {
669        const item = document.getElementById('st-' + i);
670        if (!item) continue;
671
672        item.classList.remove('active', 'completed');
673
674        if (i === activeStep) {
675            item.classList.add('active');
676        } else if (i < activeStep) {
677            item.classList.add('completed');
678        }
679    }
680}
681
682/**
683 * Allows clicking a stepper circle to jump directly to a step
684 */
685function jumpToStep(n) {
686    const targetStep = document.getElementById('step-' + n);
687    if (targetStep) {
688        const header = targetStep.querySelector('.step-header');
689        toggleStep(header);
690    }
691}
692
693/**
694 *  stop the row click from triggering when clicking the note field
695 */
696function stopNotePropagation(e) {
697    if (e && e.stopPropagation) {
698        e.stopPropagation();
699    }
700}
701
702
703function saveQuickNote(leadId, element) {
704    const noteValue = element.value;
705    const wrapper = element.closest('.comment-wrapper');
706
707    jQuery.ajax({
708        type: "POST",
709        url: "/wp-content/themes/biomotors-child/core/ajax/valideContactLead.php",
710        data: {
711            lead_id: leadId,
712            lead_note_rappel: noteValue
713        },
714        success: function() {
715            // VISUAL FEEDBACK
716            wrapper.classList.add('comment-saved');
717
718            // Remove the effect after 2 seconds
719            setTimeout(() => {
720                wrapper.classList.remove('comment-saved');
721            }, 2000);
722        },
723        error: function() {
724            element.style.borderColor = "red";
725        }
726    });
727}
728
729// document.addEventListener('DOMContentLoaded', () => {
730//     const toggleBtn = document.getElementById('darkModeToggle');
731//     const themeIcon = document.getElementById('themeIcon');
732//     const themeText = document.getElementById('themeText');
733//     const body = document.body;
734//
735//     // 1. Check for saved preference
736//     const currentTheme = localStorage.getItem('theme');
737//     if (currentTheme === 'dark') {
738//         body.classList.add('dark-mode');
739//         updateToggleUI(true);
740//     }
741//
742//     // 2. Listen for clicks
743//     toggleBtn.addEventListener('click', () => {
744//         body.classList.toggle('dark-mode');
745//
746//         let isDark = body.classList.contains('dark-mode');
747//         localStorage.setItem('theme', isDark ? 'dark' : 'light');
748//
749//         updateToggleUI(isDark);
750//     });
751//
752//     function updateToggleUI(isDark) {
753//         themeIcon.innerText = isDark ? '☀️' : '🌙';
754//         themeText.innerText = isDark ? 'Mode Clair' : 'Mode Sombre';
755//     }
756// });
757
758document.addEventListener('DOMContentLoaded', function() {
759    const btn = document.getElementById('darkModeToggle');
760    if (!btn) return; // Exit if button is hidden (not itdesk)
761
762    const icon = document.getElementById('themeIcon');
763    const text = document.getElementById('themeText');
764    const body = document.body;
765
766    btn.addEventListener('click', function() {
767        // Toggle the class
768        body.classList.toggle('dark-mode');
769
770        // Update Button Appearance based on the NEW state
771        if (body.classList.contains('dark-mode')) {
772            icon.textContent = '☀️';
773            text.textContent = 'Mode Clair';
774            btn.style.background = 'var(--white)';
775        } else {
776            icon.textContent = '🌙';
777            text.textContent = 'Mode Sombre';
778            btn.style.background = 'var(--bm-green)';
779        }
780    });
781});
782
783function toggleNote(btn) {
784    const wrapper = btn.closest('.comment-wrapper');
785    const textarea = wrapper.querySelector('.lead-comment-input');
786
787    textarea.classList.toggle('is-expanded');
788
789    if (textarea.classList.contains('is-expanded')) {
790        btn.textContent = 'Réduire';
791    } else {
792        btn.textContent = 'Voir +';
793    }
794}
795//////LECLERC
796// let currentLeclercPrice = 690;
797
798/**
799 * Handles product selection for Leclerc users
800 * @param {HTMLElement} element - The clicked product card
801 * @param {string} type - 'standard' or 'air'
802 * @param {number} price - 690 or 890
803 */
804function selectProductLeclerc(element, type, price) {
805    const step4 = document.getElementById('step-4');
806    const cards = step4.querySelectorAll('.product-card');
807    cards.forEach(card => card.classList.remove('selected'));
808    element.classList.add('selected');
809    currentLeclercPrice = price;
810    updateLeclercPriceDisplay();
811}
812// function updateLeclercPriceDisplay() {
813//     //  Grab the monthly savings from Step 3
814//     const savingsElement = document.getElementById('savMonth');
815//     const monthlySavings = parseInt(savingsElement.textContent.replace(/[^0-9]/g, '')) || 1;
816//
817//     // Calculate the new ROI
818//     const roiMonths = Math.ceil(currentLeclercPrice / monthlySavings);
819//
820//     //Update the Summary Boxes
821//     const priceBox = document.getElementById('mPriceLeclerc');
822//     const roiBox = document.getElementById('mROILeclerc');
823//
824//     if (priceBox) priceBox.textContent = currentLeclercPrice + ' €';
825//     if (roiBox) roiBox.textContent = roiMonths + ' mois';
826//
827//     //Update the Pitch text
828//     document.querySelectorAll('.dynPTTC').forEach(el => el.textContent = currentLeclercPrice + ' €');
829//     document.querySelectorAll('.dynROI').forEach(el => el.textContent = roiMonths + ' mois');
830//
831//     if (typeof updateFinancingDisplay === 'function') updateFinancingDisplay();
832// }
833
834function updateLeclercPriceDisplay() {
835    const savingsElement = document.getElementById('savMonth');
836    const monthlySavings = parseInt(savingsElement.textContent.replace(/[^0-9]/g, '')) || 1;
837
838    // ROI calculation based on whichever price is currently in the "bucket"
839    const roiMonths = Math.ceil(currentLeclercPrice / monthlySavings);
840
841    // Update Step 4 Summary Boxes
842    const priceBox = document.getElementById('mPriceLeclerc');
843    const roiBox = document.getElementById('mROILeclerc');
844
845    if (priceBox) priceBox.textContent = currentLeclercPrice + ' €';
846    if (roiBox) roiBox.textContent = roiMonths + ' mois';
847
848    // Update all dynamic text spans (The Pitch)
849    document.querySelectorAll('.dynPTTC').forEach(el => el.textContent = currentLeclercPrice + ' €');
850    document.querySelectorAll('.dynROI').forEach(el => el.textContent = roiMonths + ' mois');
851
852    // IMPORTANT: This pushes the new price into Step 5
853    if (typeof updateFinancingDisplay === 'function') {
854        updateFinancingDisplay();
855    }
856}
857
858let currentFinNb = 10; // Default to 10x
859const LECLERC_FINANCING = {
860    690: { 4: 172.50, 10: 69, 12: 57.50 },
861    890: { 4: 222.50, 10: 89, 12: 74.17 }
862};
863
864function selectFinancing(nb, card) {
865    currentFinNb = nb;
866
867    // 1. UI: Handle the visual highlight
868    const grid = document.getElementById('finGrid');
869    if (grid) {
870        grid.querySelectorAll('.fin-card').forEach(c => {
871            // Reset all cards to the "unselected" style
872            c.classList.remove('selected');
873            c.style.background = "rgba(82,183,136,0.04)";
874            c.style.border = "2px solid rgba(82,183,136,0.1)";
875            c.style.boxShadow = "none";
876        });
877
878        // Apply the "selected" style to the clicked card
879        card.classList.add('selected');
880        card.style.background = "rgba(82,183,136,0.1)";
881        card.style.border = "2px solid #52b788";
882        card.style.boxShadow = "0 0 0 1px #52b788";
883    }
884
885    // 2. Update the math and text
886    updateFinancingDisplay();
887}
888
889let currentLeclercPrice = parseFloat(bmSettings.priceLeclercStandard) || 690;
890function updateFinancingDisplay() {
891    const finGrid = document.getElementById('finGrid');
892    if (!finGrid) return;
893
894    const savingsText = document.getElementById('savMonth').textContent;
895    const monthlySavings = parseInt(savingsText.replace(/[^0-9]/g, '')) || 0;
896
897    // A. CALCULATE installments on the fly
898    const pay4  = currentLeclercPrice / 4;
899    const pay10 = currentLeclercPrice / 10;
900    const pay12 = currentLeclercPrice / 12;
901
902    // B. Update the Grid monthly values (the text inside the cards)
903    document.getElementById('fin0m').textContent = currentLeclercPrice + ' €';
904    document.getElementById('fin4m').textContent = pay4.toFixed(2).replace('.', ',') + ' €/mois';
905    document.getElementById('fin10m').textContent = pay10.toFixed(2).replace('.', ',') + ' €/mois';
906    document.getElementById('fin12m').textContent = pay12.toFixed(2).replace('.', ',') + ' €/mois';
907
908    // C. Get the payment for the CURRENTLY SELECTED term (4, 10, or 12)
909    let monthlyPayment = 0;
910    if (currentFinNb === 4) monthlyPayment = pay4;
911    else if (currentFinNb === 10) monthlyPayment = pay10;
912    else if (currentFinNb === 12) monthlyPayment = pay12;
913
914    // D. Update Bilan Mensuel (Timeline)
915    const detailBlock = document.getElementById('finDetailBlock');
916    if (currentFinNb === 0) {
917        detailBlock.style.display = 'none';
918    } else {
919        detailBlock.style.display = 'block';
920        const gainDuring = monthlySavings - monthlyPayment;
921
922        document.getElementById('finNbLabel').textContent = currentFinNb + '×';
923        document.getElementById('finDuringVal').textContent = (gainDuring >= 0 ? '+' : '') + Math.round(gainDuring) + ' €/mois';
924        document.getElementById('finAfterVal').textContent = '+' + monthlySavings + ' €/mois';
925
926        document.getElementById('finDuringSub').textContent = gainDuring >= 0 ? "Vous gagnez déjà de l'argent" : "Un petit effort temporaire";
927        document.getElementById('finDuringVal').style.color = gainDuring >= 0 ? "#f4a261" : "#e74c3c";
928    }
929
930    // E. Update Pitch Text at the bottom
931    const pitchNb = document.getElementById('finPitchNb');
932    const pitchMonthly = document.getElementById('finPitchMonthly');
933    const pitchGain = document.getElementById('finPitchGain');
934
935    if (pitchNb) pitchNb.textContent = currentFinNb === 0 ? "comptant" : currentFinNb + " fois";
936    if (pitchMonthly) pitchMonthly.textContent = (currentFinNb === 0 ? currentLeclercPrice : monthlyPayment.toFixed(2).replace('.', ',')) + " €";
937    if (pitchGain) {
938        const totalGain = Math.round(monthlySavings - monthlyPayment);
939        pitchGain.textContent = (totalGain >= 0 ? totalGain : 0) + " €";
940    }
941}
942// function updateFinancingDisplay() {
943//     // A. Get current savings from Step 3
944//     const savingsText = document.getElementById('savMonth').textContent;
945//     const monthlySavings = parseInt(savingsText.replace(/[^0-9]/g, '')) || 0;
946//
947//     // B. Get monthly payment for the selected product and term
948//     const productTerms = LECLERC_FINANCING[currentLeclercPrice];
949//     const monthlyPayment = currentFinNb === 0 ? 0 : productTerms[currentFinNb];
950//
951//     // C. Calculate Grid monthly values (so they update if product changes)
952//     document.getElementById('fin0m').textContent = currentLeclercPrice + ' €';
953//     document.getElementById('fin4m').textContent = productTerms[4].toFixed(2).replace('.', ',') + ' €/mois';
954//     document.getElementById('fin10m').textContent = productTerms[10].toFixed(2).replace('.', ',') + ' €/mois';
955//     document.getElementById('fin12m').textContent = productTerms[12].toFixed(2).replace('.', ',') + ' €/mois';
956//
957//     // D. Update Bilan Mensuel (Timeline)
958//     const detailBlock = document.getElementById('finDetailBlock');
959//     if (currentFinNb === 0) {
960//         detailBlock.style.display = 'none';
961//     } else {
962//         detailBlock.style.display = 'block';
963//         const gainDuring = monthlySavings - monthlyPayment;
964//
965//         document.getElementById('finNbLabel').textContent = currentFinNb + '×';
966//         document.getElementById('finDuringVal').textContent = (gainDuring >= 0 ? '+' : '') + Math.round(gainDuring) + ' €/mois';
967//         document.getElementById('finAfterVal').textContent = '+' + monthlySavings + ' €/mois';
968//
969//         // Update labels based on gain
970//         document.getElementById('finDuringSub').textContent = gainDuring >= 0 ? "Vous gagnez déjà de l'argent" : "Un petit effort temporaire";
971//         document.getElementById('finDuringVal').style.color = gainDuring >= 0 ? "#f4a261" : "#e74c3c";
972//     }
973//
974//     // E. Update Pitch Text
975//     const pitchNb = document.getElementById('finPitchNb');
976//     const pitchMonthly = document.getElementById('finPitchMonthly');
977//     const pitchGain = document.getElementById('finPitchGain');
978//
979//     if (pitchNb) pitchNb.textContent = currentFinNb === 0 ? "comptant" : currentFinNb + " fois";
980//     if (pitchMonthly) pitchMonthly.textContent = (currentFinNb === 0 ? currentLeclercPrice : monthlyPayment.toFixed(2).replace('.', ',')) + " €";
981//     if (pitchGain) {
982//         const totalGain = Math.round(monthlySavings - monthlyPayment);
983//         pitchGain.textContent = (totalGain >= 0 ? totalGain : 0) + " €";
984//     }
985// }

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.