Yocapp commited on
Commit
311c1b8
·
verified ·
1 Parent(s): 2fdf2f9

🐳 19/03 - 14:49 - CONTEXTE IMPORTANT :Tu travailles sur un fichier HTML déjà existant contenant un simulateur VTC fonctionnel en multi-étapes.⚠️ RÈGLES STRICTES :- NE PAS recréer la page- NE PAS dupl

Browse files
Files changed (1) hide show
  1. simulateur-pertes.html +404 -65
simulateur-pertes.html CHANGED
@@ -109,6 +109,16 @@
109
  <input type="number" id="daysPerMonth" placeholder="Ex : 22"
110
  class="w-full rounded-xl bg-black/40 border border-white/10 px-4 py-3 text-white text-sm outline-none focus:border-emerald-500/50 transition">
111
  </div>
 
 
 
 
 
 
 
 
 
 
112
  </div>
113
 
114
  <div class="text-xs text-white/50 mb-4 bg-white/5 rounded-lg p-3">
@@ -198,7 +208,7 @@
198
  <div class="bg-[#151821] border border-white/10 rounded-2xl p-5">
199
  <h2 class="text-lg font-medium text-white mb-4">Votre véhicule</h2>
200
 
201
- <div class="grid grid-cols-3 gap-2 mb-6" id="vehicleButtons">
202
  <button type="button" data-value="thermique" class="vehicle-btn py-3 rounded-xl border border-white/10 text-white/60 bg-transparent transition text-sm font-medium">
203
  Thermique
204
  </button>
@@ -211,6 +221,13 @@
211
  </div>
212
  <input type="hidden" id="vehicleType" value="">
213
 
 
 
 
 
 
 
 
214
  <div class="flex gap-3">
215
  <button id="btnStep5Back" class="w-1/3 py-3 rounded-xl bg-white/5 text-white border border-white/10 text-sm font-medium hover:bg-white/10 transition">
216
  Retour
@@ -292,12 +309,62 @@
292
  Découvrir mon score TIE
293
  </button>
294
 
 
 
 
 
295
  <button id="btnReset" class="w-full py-2 mt-3 text-xs text-white/60 hover:text-white transition">
296
  Refaire le calcul
297
  </button>
298
  </div>
299
  </div>
300
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
  <!-- Score Loading -->
302
  <div id="scoreLoading" class="loading-container">
303
  <div class="bg-[#151821] border border-white/10 rounded-2xl p-8 text-center">
@@ -371,10 +438,10 @@
371
 
372
  <!-- CTAs -->
373
  <div class="space-y-3 mb-6">
374
- <button class="w-full py-3 rounded-xl bg-emerald-500 text-black font-medium text-sm hover:opacity-90 transition">
375
- Optimiser mes courses
376
  </button>
377
- <button class="w-full py-3 rounded-xl bg-white/5 border border-white/10 text-white font-medium text-sm hover:bg-white/10 transition">
378
  Développer mes clients privés
379
  </button>
380
  </div>
@@ -397,13 +464,18 @@
397
  let formData = {
398
  coursesPerDay: 0,
399
  daysPerMonth: 0,
 
 
400
  platformRevenue: 0,
401
  privateRevenue: 0,
402
  tipsRevenue: 0,
403
- vehicleType: ''
 
404
  };
405
 
406
  let calculationResults = {};
 
 
407
 
408
  // DOM Elements check helper
409
  function getElement(id) {
@@ -504,6 +576,8 @@
504
  // Step 1 Logic
505
  const coursesPerDayInput = getElement('coursesPerDay');
506
  const daysPerMonthInput = getElement('daysPerMonth');
 
 
507
  const totalCoursesPreview = getElement('totalCoursesPreview');
508
  const btnStep1Next = getElement('btnStep1Next');
509
 
@@ -525,10 +599,12 @@
525
 
526
  if (btnStep1Next) {
527
  btnStep1Next.addEventListener('click', () => {
528
- if (!coursesPerDayInput || !daysPerMonthInput) return;
529
 
530
  const courses = parseFloat(coursesPerDayInput.value);
531
  const days = parseFloat(daysPerMonthInput.value);
 
 
532
 
533
  if (!courses || courses <= 0) {
534
  alert('Veuillez entrer un nombre de courses valide.');
@@ -538,9 +614,19 @@
538
  alert('Veuillez entrer un nombre de jours valide (1-31).');
539
  return;
540
  }
 
 
 
 
 
 
 
 
541
 
542
  formData.coursesPerDay = courses;
543
  formData.daysPerMonth = days;
 
 
544
  showStep(2);
545
  });
546
  }
@@ -610,6 +696,7 @@
610
  // Step 5 Logic
611
  const vehicleBtns = document.querySelectorAll('.vehicle-btn');
612
  const vehicleTypeInput = getElement('vehicleType');
 
613
  const btnStep5Back = getElement('btnStep5Back');
614
  const btnStep5Calculate = getElement('btnStep5Calculate');
615
 
@@ -622,7 +709,7 @@
622
  if (vehicleTypeInput) vehicleTypeInput.value = value;
623
  formData.vehicleType = value;
624
 
625
- if (btnStep5Calculate) btnStep5Calculate.disabled = false;
626
  });
627
  });
628
 
@@ -630,52 +717,155 @@
630
  btnStep5Back.addEventListener('click', () => showStep(4));
631
  }
632
 
633
- // Calculation Logic
634
  function calculateResults() {
635
- const courses = formData.coursesPerDay * formData.daysPerMonth;
636
  const caTotal = formData.platformRevenue + formData.privateRevenue + formData.tipsRevenue;
637
- const kmTotal = courses * 10;
638
-
639
- // Cost per km
640
- let costPerKm = 0.35; // thermique default
641
- if (formData.vehicleType === 'hybride') costPerKm = 0.28;
642
- if (formData.vehicleType === 'electrique') costPerKm = 0.20;
643
-
644
- const costKm = kmTotal * costPerKm;
645
- const charges = caTotal * 0.22;
646
 
647
- // Hidden losses (bad trips)
648
- const badTripRate = 0.35;
649
- const lossPerTrip = 3;
650
- const hiddenLoss = courses * badTripRate * lossPerTrip;
651
-
652
- const profit = caTotal - charges - costKm;
653
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
654
  // Percentages
655
  const pctPlatform = caTotal > 0 ? (formData.platformRevenue / caTotal) * 100 : 0;
656
  const pctPrivate = caTotal > 0 ? (formData.privateRevenue / caTotal) * 100 : 0;
657
  const pctTips = caTotal > 0 ? (formData.tipsRevenue / caTotal) * 100 : 0;
658
 
659
- // Private projection (+15%)
660
- const privateGain = formData.privateRevenue * 0.15;
661
-
662
  calculationResults = {
663
- courses,
664
  caTotal,
665
- kmTotal,
666
- costKm,
667
- charges,
668
- hiddenLoss,
 
669
  profit,
 
670
  pctPlatform,
671
  pctPrivate,
672
  pctTips,
673
- privateGain
674
  };
675
 
676
  return calculationResults;
677
  }
678
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
679
  // TIE Score Calculation
680
  function calculateTIEScore() {
681
  const results = calculationResults;
@@ -768,6 +958,8 @@
768
 
769
  function displayResults() {
770
  const results = calculationResults;
 
 
771
 
772
  // Update UI
773
  const resultIcon = getElement('resultIcon');
@@ -783,40 +975,65 @@
783
  const privateProjection = getElement('privateProjection');
784
  const gainProjection = getElement('gainProjection');
785
 
 
786
  if (profitAmount) profitAmount.textContent = formatMoney(results.profit);
787
  if (caTotal) caTotal.textContent = formatMoney(results.caTotal);
788
- if (chargesAmount) chargesAmount.textContent = '-' + formatMoney(results.charges);
789
- if (kmCost) kmCost.textContent = '-' + formatMoney(results.costKm);
790
  if (pctPlatform) pctPlatform.textContent = Math.round(results.pctPlatform) + '%';
791
  if (pctPrivate) pctPrivate.textContent = Math.round(results.pctPrivate) + '%';
792
  if (pctTips) pctTips.textContent = Math.round(results.pctTips) + '%';
793
 
794
- // Result logic
795
- if (results.profit < 1500) {
796
  if (resultIcon) resultIcon.textContent = '💸';
797
- if (resultTitle) resultTitle.textContent = `Vous perdez environ ${Math.abs(Math.round(results.profit - 1500))}€/mois`;
798
- if (resultSubtitle) resultSubtitle.textContent = "Votre activité n'est pas rentable actuellement.";
799
- } else if (results.profit <= 3000) {
800
  if (resultIcon) resultIcon.textContent = '⚖️';
801
- if (resultTitle) resultTitle.textContent = "Votre activité est stable mais optimisable";
802
- if (resultSubtitle) resultSubtitle.textContent = "Vous gagnez correctement mais pouvez optimiser davantage.";
803
  } else {
804
  if (resultIcon) resultIcon.textContent = '🚀';
805
- if (resultTitle) resultTitle.textContent = "Votre activité est rentable et bien optimisée";
806
- if (resultSubtitle) resultSubtitle.textContent = "Excellent travail ! Votre modèle économique est solide.";
807
  }
808
 
809
- // Private projection
810
- if (results.privateGain > 0) {
811
- if (privateProjection) privateProjection.classList.remove('hidden');
812
- if (gainProjection) gainProjection.textContent = formatMoney(results.privateGain);
 
 
 
 
 
 
 
 
 
 
813
  } else {
814
- if (privateProjection) privateProjection.classList.add('hidden');
 
 
 
 
 
 
 
 
 
 
 
815
  }
 
 
 
816
  }
817
 
818
  function displayScore() {
819
- const score = calculateTIEScore();
820
 
821
  const tieScore = getElement('tieScore');
822
  const scoreLabel = getElement('scoreLabel');
@@ -838,20 +1055,54 @@
838
  if (scoreLabel) scoreLabel.textContent = score.label;
839
  if (scoreInterpretation) scoreInterpretation.textContent = score.interpretation;
840
 
841
- if (scoreProfit) scoreProfit.textContent = `${score.profit}/40`;
842
- if (scoreMix) scoreMix.textContent = `${score.mix}/20`;
843
- if (scoreQuality) scoreQuality.textContent = `${score.quality}/25`;
844
- if (scoreEfficiency) scoreEfficiency.textContent = `${score.efficiency}/15`;
845
-
846
- if (barProfit) barProfit.style.width = (score.profit / 40 * 100) + '%';
847
- if (barMix) barMix.style.width = (score.mix / 20 * 100) + '%';
848
- if (barQuality) barQuality.style.width = (score.quality / 25 * 100) + '%';
849
- if (barEfficiency) barEfficiency.style.width = (score.efficiency / 15 * 100) + '%';
850
-
851
- if (scoreProjectionText && score.pointsToGain > 0) {
852
- scoreProjectionText.textContent = `+${score.pointsToGain} points possibles en développant vos courses privées`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
853
  } else if (scoreProjectionText) {
854
- scoreProjectionText.textContent = "Votre mix privé/plateforme est déjà optimal";
855
  }
856
  }
857
 
@@ -862,6 +1113,11 @@
862
  alert('Veuillez sélectionner un type de véhicule.');
863
  return;
864
  }
 
 
 
 
 
865
 
866
  showLoading();
867
 
@@ -876,10 +1132,58 @@
876
  });
877
  }
878
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
879
  // Discover score button
880
  const btnDiscoverScore = getElement('btnDiscoverScore');
881
  if (btnDiscoverScore) {
882
  btnDiscoverScore.addEventListener('click', () => {
 
 
 
883
  showScoreLoading();
884
 
885
  setTimeout(() => {
@@ -889,6 +1193,16 @@
889
  });
890
  }
891
 
 
 
 
 
 
 
 
 
 
 
892
  // Reset button
893
  const btnReset = getElement('btnReset');
894
  if (btnReset) {
@@ -897,18 +1211,24 @@
897
  formData = {
898
  coursesPerDay: 0,
899
  daysPerMonth: 0,
 
 
900
  platformRevenue: 0,
901
  privateRevenue: 0,
902
  tipsRevenue: 0,
903
- vehicleType: ''
 
904
  };
905
 
906
  // Clear inputs
907
  if (coursesPerDayInput) coursesPerDayInput.value = '';
908
  if (daysPerMonthInput) daysPerMonthInput.value = '';
 
 
909
  if (platformRevenueInput) platformRevenueInput.value = '';
910
  if (privateRevenueInput) privateRevenueInput.value = '';
911
  if (tipsRevenueInput) tipsRevenueInput.value = '';
 
912
 
913
  vehicleBtns.forEach(b => b.classList.remove('selected'));
914
  if (vehicleTypeInput) vehicleTypeInput.value = '';
@@ -916,6 +1236,9 @@
916
 
917
  if (totalCoursesPreview) totalCoursesPreview.textContent = '0';
918
 
 
 
 
919
  // Go back to step 1
920
  showStep(1);
921
  });
@@ -932,6 +1255,22 @@
932
 
933
  if (daysPerMonthInput) {
934
  daysPerMonthInput.addEventListener('keypress', (e) => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
935
  if (e.key === 'Enter' && btnStep1Next) btnStep1Next.click();
936
  });
937
  }
 
109
  <input type="number" id="daysPerMonth" placeholder="Ex : 22"
110
  class="w-full rounded-xl bg-black/40 border border-white/10 px-4 py-3 text-white text-sm outline-none focus:border-emerald-500/50 transition">
111
  </div>
112
+ <div>
113
+ <label class="block text-sm text-white/80 mb-2">Heures travaillées dans le mois</label>
114
+ <input type="number" id="hoursPerMonth" placeholder="Ex : 180"
115
+ class="w-full rounded-xl bg-black/40 border border-white/10 px-4 py-3 text-white text-sm outline-none focus:border-emerald-500/50 transition">
116
+ </div>
117
+ <div>
118
+ <label class="block text-sm text-white/80 mb-2">Distance mensuelle estimée (km)</label>
119
+ <input type="number" id="distancePerMonth" placeholder="Ex : 2500"
120
+ class="w-full rounded-xl bg-black/40 border border-white/10 px-4 py-3 text-white text-sm outline-none focus:border-emerald-500/50 transition">
121
+ </div>
122
  </div>
123
 
124
  <div class="text-xs text-white/50 mb-4 bg-white/5 rounded-lg p-3">
 
208
  <div class="bg-[#151821] border border-white/10 rounded-2xl p-5">
209
  <h2 class="text-lg font-medium text-white mb-4">Votre véhicule</h2>
210
 
211
+ <div class="grid grid-cols-3 gap-2 mb-4" id="vehicleButtons">
212
  <button type="button" data-value="thermique" class="vehicle-btn py-3 rounded-xl border border-white/10 text-white/60 bg-transparent transition text-sm font-medium">
213
  Thermique
214
  </button>
 
221
  </div>
222
  <input type="hidden" id="vehicleType" value="">
223
 
224
+ <div class="mb-4">
225
+ <label class="block text-sm text-white/80 mb-2">Mensualité crédit / leasing (€)</label>
226
+ <input type="number" id="vehiclePayment" placeholder="Ex : 350"
227
+ class="w-full rounded-xl bg-black/40 border border-white/10 px-4 py-3 text-white text-sm outline-none focus:border-emerald-500/50 transition">
228
+ <p class="text-xs text-white/40 mt-2">Mettre 0 si véhicule déjà payé</p>
229
+ </div>
230
+
231
  <div class="flex gap-3">
232
  <button id="btnStep5Back" class="w-1/3 py-3 rounded-xl bg-white/5 text-white border border-white/10 text-sm font-medium hover:bg-white/10 transition">
233
  Retour
 
309
  Découvrir mon score TIE
310
  </button>
311
 
312
+ <button id="btnShowDetails" class="w-full py-3 rounded-xl bg-white/5 border border-white/10 text-white font-medium text-sm hover:bg-white/10 transition mb-2">
313
+ Voir le détail des coûts
314
+ </button>
315
+
316
  <button id="btnReset" class="w-full py-2 mt-3 text-xs text-white/60 hover:text-white transition">
317
  Refaire le calcul
318
  </button>
319
  </div>
320
  </div>
321
 
322
+ <!-- Details Section (Collapsible) -->
323
+ <div id="detailsSection" class="step-container">
324
+ <div class="bg-[#151821] border border-white/10 rounded-2xl p-5 mb-4">
325
+ <h3 class="text-base font-medium text-white mb-4">Détail de votre activité</h3>
326
+
327
+ <div class="space-y-3 mb-4 text-sm">
328
+ <div class="flex justify-between text-white/60">
329
+ <span>Chiffre d'affaires total</span>
330
+ <span id="detailCA" class="text-white font-medium">0 €</span>
331
+ </div>
332
+ <div class="flex justify-between text-white/60">
333
+ <span>Charges sociales (22%)</span>
334
+ <span id="detailSocial" class="text-red-400">-0 €</span>
335
+ </div>
336
+ <div class="flex justify-between text-white/60">
337
+ <span>Coût variable (${calculationResults.costPerKm}€/km)</span>
338
+ <span id="detailVariable" class="text-red-400">-0 €</span>
339
+ </div>
340
+ <div class="flex justify-between text-white/60">
341
+ <span>Assurance (fixe)</span>
342
+ <span id="detailInsurance" class="text-red-400">-200 €</span>
343
+ </div>
344
+ <div class="flex justify-between text-white/60">
345
+ <span>Mensualité véhicule</span>
346
+ <span id="detailPayment" class="text-red-400">-0 €</span>
347
+ </div>
348
+ <div class="border-t border-white/10 pt-3 flex justify-between text-white font-medium">
349
+ <span>BÉNÉFICE NET</span>
350
+ <span id="detailProfit" class="text-emerald-400">0 €</span>
351
+ </div>
352
+ </div>
353
+
354
+ <div class="bg-black/40 rounded-lg p-3 mb-4">
355
+ <div class="flex justify-between text-sm mb-1">
356
+ <span class="text-white/60">Taux horaire réel</span>
357
+ <span id="detailHourly" class="text-white font-medium">0 €/h</span>
358
+ </div>
359
+ <div class="text-xs text-white/40">Basé sur ${formData.hoursPerMonth}h travaillées</div>
360
+ </div>
361
+
362
+ <button id="btnBackFromDetails" class="w-full py-3 rounded-xl bg-white/5 border border-white/10 text-white font-medium text-sm hover:bg-white/10 transition">
363
+ Retour aux résultats
364
+ </button>
365
+ </div>
366
+ </div>
367
+
368
  <!-- Score Loading -->
369
  <div id="scoreLoading" class="loading-container">
370
  <div class="bg-[#151821] border border-white/10 rounded-2xl p-8 text-center">
 
438
 
439
  <!-- CTAs -->
440
  <div class="space-y-3 mb-6">
441
+ <button id="btnBackFromScore" class="w-full py-3 rounded-xl bg-white/5 border border-white/10 text-white font-medium text-sm hover:bg-white/10 transition">
442
+ Retour à mes résultats
443
  </button>
444
+ <button class="w-full py-3 rounded-xl bg-emerald-500 text-black font-medium text-sm hover:opacity-90 transition">
445
  Développer mes clients privés
446
  </button>
447
  </div>
 
464
  let formData = {
465
  coursesPerDay: 0,
466
  daysPerMonth: 0,
467
+ hoursPerMonth: 0,
468
+ distancePerMonth: 0,
469
  platformRevenue: 0,
470
  privateRevenue: 0,
471
  tipsRevenue: 0,
472
+ vehicleType: '',
473
+ vehiclePayment: 0
474
  };
475
 
476
  let calculationResults = {};
477
+ let tieProjection = {};
478
+ let tieScoreData = {};
479
 
480
  // DOM Elements check helper
481
  function getElement(id) {
 
576
  // Step 1 Logic
577
  const coursesPerDayInput = getElement('coursesPerDay');
578
  const daysPerMonthInput = getElement('daysPerMonth');
579
+ const hoursPerMonthInput = getElement('hoursPerMonth');
580
+ const distancePerMonthInput = getElement('distancePerMonth');
581
  const totalCoursesPreview = getElement('totalCoursesPreview');
582
  const btnStep1Next = getElement('btnStep1Next');
583
 
 
599
 
600
  if (btnStep1Next) {
601
  btnStep1Next.addEventListener('click', () => {
602
+ if (!coursesPerDayInput || !daysPerMonthInput || !hoursPerMonthInput || !distancePerMonthInput) return;
603
 
604
  const courses = parseFloat(coursesPerDayInput.value);
605
  const days = parseFloat(daysPerMonthInput.value);
606
+ const hours = parseFloat(hoursPerMonthInput.value);
607
+ const distance = parseFloat(distancePerMonthInput.value);
608
 
609
  if (!courses || courses <= 0) {
610
  alert('Veuillez entrer un nombre de courses valide.');
 
614
  alert('Veuillez entrer un nombre de jours valide (1-31).');
615
  return;
616
  }
617
+ if (!hours || hours <= 0) {
618
+ alert('Veuillez entrer un nombre d\'heures valide.');
619
+ return;
620
+ }
621
+ if (!distance || distance <= 0) {
622
+ alert('Veuillez entrer une distance valide.');
623
+ return;
624
+ }
625
 
626
  formData.coursesPerDay = courses;
627
  formData.daysPerMonth = days;
628
+ formData.hoursPerMonth = hours;
629
+ formData.distancePerMonth = distance;
630
  showStep(2);
631
  });
632
  }
 
696
  // Step 5 Logic
697
  const vehicleBtns = document.querySelectorAll('.vehicle-btn');
698
  const vehicleTypeInput = getElement('vehicleType');
699
+ const vehiclePaymentInput = getElement('vehiclePayment');
700
  const btnStep5Back = getElement('btnStep5Back');
701
  const btnStep5Calculate = getElement('btnStep5Calculate');
702
 
 
709
  if (vehicleTypeInput) vehicleTypeInput.value = value;
710
  formData.vehicleType = value;
711
 
712
+ if (btnStep5Calculate && formData.vehicleType) btnStep5Calculate.disabled = false;
713
  });
714
  });
715
 
 
717
  btnStep5Back.addEventListener('click', () => showStep(4));
718
  }
719
 
720
+ // Calculation Logic - New Model
721
  function calculateResults() {
722
+ // A. CA Total
723
  const caTotal = formData.platformRevenue + formData.privateRevenue + formData.tipsRevenue;
 
 
 
 
 
 
 
 
 
724
 
725
+ // B. Variable costs based on vehicle type
726
+ let costPerKm = 0.35; // thermique
727
+ if (formData.vehicleType === 'hybride') costPerKm = 0.25;
728
+ if (formData.vehicleType === 'electrique') costPerKm = 0.15;
729
+
730
+ const variableCost = formData.distancePerMonth * costPerKm;
731
+
732
+ // C. Fixed charges
733
+ const insuranceFixed = 200;
734
+ const vehiclePayment = formData.vehiclePayment || 0;
735
+ const fixedCharges = insuranceFixed + vehiclePayment;
736
+
737
+ // D. Social charges (22% of CA)
738
+ const socialCharges = caTotal * 0.22;
739
+
740
+ // E. Profit
741
+ const profit = caTotal - socialCharges - variableCost - fixedCharges;
742
+
743
+ // F. Hourly rate
744
+ const hourlyRate = formData.hoursPerMonth > 0 ? profit / formData.hoursPerMonth : 0;
745
+
746
  // Percentages
747
  const pctPlatform = caTotal > 0 ? (formData.platformRevenue / caTotal) * 100 : 0;
748
  const pctPrivate = caTotal > 0 ? (formData.privateRevenue / caTotal) * 100 : 0;
749
  const pctTips = caTotal > 0 ? (formData.tipsRevenue / caTotal) * 100 : 0;
750
 
 
 
 
751
  calculationResults = {
 
752
  caTotal,
753
+ variableCost,
754
+ fixedCharges,
755
+ socialCharges,
756
+ insuranceFixed,
757
+ vehiclePayment,
758
  profit,
759
+ hourlyRate,
760
  pctPlatform,
761
  pctPrivate,
762
  pctTips,
763
+ costPerKm
764
  };
765
 
766
  return calculationResults;
767
  }
768
 
769
+ // TIE Projection Logic
770
+ function calculateTIEProjection() {
771
+ const currentPrivateRatio = calculationResults.pctPrivate;
772
+ const caTotal = calculationResults.caTotal;
773
+
774
+ // If private is less than 30%, calculate potential
775
+ if (currentPrivateRatio < 30) {
776
+ // Target: 30% of CA as private
777
+ const targetPrivateRevenue = caTotal * 0.30;
778
+ const currentPrivateRevenue = formData.privateRevenue;
779
+ const additionalPrivateRevenue = targetPrivateRevenue - currentPrivateRevenue;
780
+
781
+ // Private base rate: 2€/km
782
+ // But we need to calculate the gain based on better margins
783
+ // Private has no commission (vs platform which has ~20-25%)
784
+ // So the gain is roughly the commission difference on the additional amount
785
+
786
+ const avgCommissionRate = 0.22; // Average platform commission
787
+ const gainFromConversion = additionalPrivateRevenue * avgCommissionRate;
788
+
789
+ // Recalculate total profit with new private ratio
790
+ const newCaTotal = caTotal; // Same total, just redistributed
791
+ const newSocialCharges = newCaTotal * 0.22;
792
+ const newProfit = newCaTotal - newSocialCharges - calculationResults.variableCost - calculationResults.fixedCharges;
793
+ const monthlyGain = newProfit - calculationResults.profit;
794
+
795
+ tieProjection = {
796
+ hasPotential: true,
797
+ currentPrivateRatio: currentPrivateRatio,
798
+ targetPrivateRatio: 30,
799
+ monthlyGain: monthlyGain > 0 ? monthlyGain : 0,
800
+ message: `En passant à 30% de revenus privés, vous pourriez gagner ${monthlyGain > 0 ? Math.round(monthlyGain) : 0}€ de plus par mois`
801
+ };
802
+ } else {
803
+ tieProjection = {
804
+ hasPotential: false,
805
+ currentPrivateRatio: currentPrivateRatio,
806
+ message: "Votre mix privé/plateforme est déjà optimal (30% ou plus)"
807
+ };
808
+ }
809
+
810
+ return tieProjection;
811
+ }
812
+
813
+ // TIE Score Calculation (0-100)
814
+ function calculateTIEScore() {
815
+ let score = 0;
816
+ const results = calculationResults;
817
+
818
+ // 1. Profitability (40 points max)
819
+ if (results.profit > 3000) score += 40;
820
+ else if (results.profit > 2000) score += 30;
821
+ else if (results.profit > 1500) score += 20;
822
+ else if (results.profit > 800) score += 10;
823
+ else score += 5;
824
+
825
+ // 2. Private revenue mix (20 points max)
826
+ if (results.pctPrivate >= 50) score += 20;
827
+ else if (results.pctPrivate >= 30) score += 15;
828
+ else if (results.pctPrivate >= 15) score += 10;
829
+ else if (results.pctPrivate >= 5) score += 5;
830
+ else score += 0;
831
+
832
+ // 3. Hourly rate efficiency (25 points max)
833
+ if (results.hourlyRate > 25) score += 25;
834
+ else if (results.hourlyRate > 18) score += 20;
835
+ else if (results.hourlyRate > 12) score += 15;
836
+ else if (results.hourlyRate > 8) score += 10;
837
+ else score += 5;
838
+
839
+ // 4. Vehicle efficiency (15 points max)
840
+ if (formData.vehicleType === 'electrique') score += 15;
841
+ else if (formData.vehicleType === 'hybride') score += 10;
842
+ else score += 5; // thermique
843
+
844
+ tieScoreData = {
845
+ total: score,
846
+ label: getScoreLabel(score),
847
+ interpretation: getScoreInterpretation(score)
848
+ };
849
+
850
+ return tieScoreData;
851
+ }
852
+
853
+ function getScoreLabel(score) {
854
+ if (score >= 80) return "Chauffeur Expert";
855
+ if (score >= 65) return "Bon niveau";
856
+ if (score >= 45) return "Activité stable";
857
+ if (score >= 25) return "À optimiser";
858
+ return "Activité à risque";
859
+ }
860
+
861
+ function getScoreInterpretation(score) {
862
+ if (score >= 80) return "🏆 Excellent ! Votre activité est bien optimisée et rentable.";
863
+ if (score >= 65) return "🚀 Très bien. Votre activité est rentable mais peut encore s'améliorer.";
864
+ if (score >= 45) return "⚖️ Correct. Votre activité est viable mais pourrait être plus optimisée.";
865
+ if (score >= 25) return "⚠️ Fragile. Vous devriez développer vos revenus privés et réduire les coûts.";
866
+ return "🚨 Alert. Votre rentabilité est faible. Développez vos clients privés rapidement.";
867
+ }
868
+
869
  // TIE Score Calculation
870
  function calculateTIEScore() {
871
  const results = calculationResults;
 
958
 
959
  function displayResults() {
960
  const results = calculationResults;
961
+ const projection = calculateTIEProjection();
962
+ const score = calculateTIEScore();
963
 
964
  // Update UI
965
  const resultIcon = getElement('resultIcon');
 
975
  const privateProjection = getElement('privateProjection');
976
  const gainProjection = getElement('gainProjection');
977
 
978
+ // Update new detailed breakdown
979
  if (profitAmount) profitAmount.textContent = formatMoney(results.profit);
980
  if (caTotal) caTotal.textContent = formatMoney(results.caTotal);
981
+ if (chargesAmount) chargesAmount.textContent = '-' + formatMoney(results.socialCharges);
982
+ if (kmCost) kmCost.textContent = '-' + formatMoney(results.variableCost);
983
  if (pctPlatform) pctPlatform.textContent = Math.round(results.pctPlatform) + '%';
984
  if (pctPrivate) pctPrivate.textContent = Math.round(results.pctPrivate) + '%';
985
  if (pctTips) pctTips.textContent = Math.round(results.pctTips) + '%';
986
 
987
+ // Result logic based on new calculation
988
+ if (results.profit < 800) {
989
  if (resultIcon) resultIcon.textContent = '💸';
990
+ if (resultTitle) resultTitle.textContent = `Activité non rentable`;
991
+ if (resultSubtitle) resultSubtitle.textContent = `Bénéfice mensuel : ${formatMoney(results.profit)} · Taux horaire : ${formatMoney(results.hourlyRate)}/h`;
992
+ } else if (results.profit < 2000) {
993
  if (resultIcon) resultIcon.textContent = '⚖️';
994
+ if (resultTitle) resultTitle.textContent = "Activité viable mais fragile";
995
+ if (resultSubtitle) resultSubtitle.textContent = `Bénéfice : ${formatMoney(results.profit)}/mois · ${formatMoney(results.hourlyRate)}/h`;
996
  } else {
997
  if (resultIcon) resultIcon.textContent = '🚀';
998
+ if (resultTitle) resultTitle.textContent = "Activité rentable et solide";
999
+ if (resultSubtitle) resultSubtitle.textContent = `Bénéfice : ${formatMoney(results.profit)}/mois · ${formatMoney(results.hourlyRate)}/h`;
1000
  }
1001
 
1002
+ // TIE Projection
1003
+ if (projection.hasPotential && projection.monthlyGain > 0) {
1004
+ if (privateProjection) {
1005
+ privateProjection.classList.remove('hidden');
1006
+ privateProjection.innerHTML = `
1007
+ <div class="flex items-center gap-2 text-emerald-400 font-medium text-sm mb-1">
1008
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 6 13.5 15.5 8.5 10.5 1 18"/><polyline points="17 6 23 6 23 12"/></svg>
1009
+ Potentiel TIE
1010
+ </div>
1011
+ <div class="text-sm text-white/80">
1012
+ Passer à 30% de privés = <span class="text-emerald-400 font-medium">+${Math.round(projection.monthlyGain)}€/mois</span>
1013
+ </div>
1014
+ `;
1015
+ }
1016
  } else {
1017
+ if (privateProjection) {
1018
+ privateProjection.classList.remove('hidden');
1019
+ privateProjection.innerHTML = `
1020
+ <div class="flex items-center gap-2 text-emerald-400 font-medium text-sm mb-1">
1021
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>
1022
+ Mix optimal atteint
1023
+ </div>
1024
+ <div class="text-sm text-white/80">
1025
+ ${projection.message}
1026
+ </div>
1027
+ `;
1028
+ }
1029
  }
1030
+
1031
+ // Store score for display
1032
+ window.currentTIEScore = score;
1033
  }
1034
 
1035
  function displayScore() {
1036
+ const score = window.currentTIEScore || calculateTIEScore();
1037
 
1038
  const tieScore = getElement('tieScore');
1039
  const scoreLabel = getElement('scoreLabel');
 
1055
  if (scoreLabel) scoreLabel.textContent = score.label;
1056
  if (scoreInterpretation) scoreInterpretation.textContent = score.interpretation;
1057
 
1058
+ // Calculate breakdown points
1059
+ const results = calculationResults;
1060
+ let profitPoints = 0;
1061
+ if (results.profit > 3000) profitPoints = 40;
1062
+ else if (results.profit > 2000) profitPoints = 30;
1063
+ else if (results.profit > 1500) profitPoints = 20;
1064
+ else if (results.profit > 800) profitPoints = 10;
1065
+ else profitPoints = 5;
1066
+
1067
+ let mixPoints = 0;
1068
+ if (results.pctPrivate >= 50) mixPoints = 20;
1069
+ else if (results.pctPrivate >= 30) mixPoints = 15;
1070
+ else if (results.pctPrivate >= 15) mixPoints = 10;
1071
+ else if (results.pctPrivate >= 5) mixPoints = 5;
1072
+
1073
+ let hourlyPoints = 0;
1074
+ if (results.hourlyRate > 25) hourlyPoints = 25;
1075
+ else if (results.hourlyRate > 18) hourlyPoints = 20;
1076
+ else if (results.hourlyRate > 12) hourlyPoints = 15;
1077
+ else if (results.hourlyRate > 8) hourlyPoints = 10;
1078
+ else hourlyPoints = 5;
1079
+
1080
+ let vehiclePoints = 0;
1081
+ if (formData.vehicleType === 'electrique') vehiclePoints = 15;
1082
+ else if (formData.vehicleType === 'hybride') vehiclePoints = 10;
1083
+ else vehiclePoints = 5;
1084
+
1085
+ if (scoreProfit) scoreProfit.textContent = `${profitPoints}/40`;
1086
+ if (scoreMix) scoreMix.textContent = `${mixPoints}/20`;
1087
+ if (scoreQuality) scoreQuality.textContent = `${hourlyPoints}/25`;
1088
+ if (scoreEfficiency) scoreEfficiency.textContent = `${vehiclePoints}/15`;
1089
+
1090
+ if (barProfit) barProfit.style.width = (profitPoints / 40 * 100) + '%';
1091
+ if (barMix) barMix.style.width = (mixPoints / 20 * 100) + '%';
1092
+ if (barQuality) barQuality.style.width = (hourlyPoints / 25 * 100) + '%';
1093
+ if (barEfficiency) barEfficiency.style.width = (vehiclePoints / 15 * 100) + '%';
1094
+
1095
+ // Points to gain
1096
+ const currentPrivateRatio = results.pctPrivate;
1097
+ let pointsToGain = 0;
1098
+ if (currentPrivateRatio < 30) {
1099
+ pointsToGain = Math.round((30 - currentPrivateRatio) / 2);
1100
+ }
1101
+
1102
+ if (scoreProjectionText && pointsToGain > 0) {
1103
+ scoreProjectionText.textContent = `+${pointsToGain} points possibles en développant vos courses privées`;
1104
  } else if (scoreProjectionText) {
1105
+ scoreProjectionText.textContent = "Votre profil est bien optimisé";
1106
  }
1107
  }
1108
 
 
1113
  alert('Veuillez sélectionner un type de véhicule.');
1114
  return;
1115
  }
1116
+
1117
+ // Get vehicle payment
1118
+ if (vehiclePaymentInput) {
1119
+ formData.vehiclePayment = parseFloat(vehiclePaymentInput.value) || 0;
1120
+ }
1121
 
1122
  showLoading();
1123
 
 
1132
  });
1133
  }
1134
 
1135
+ // Show Details button
1136
+ const btnShowDetails = getElement('btnShowDetails');
1137
+ const btnBackFromDetails = getElement('btnBackFromDetails');
1138
+ const detailsSection = getElement('detailsSection');
1139
+
1140
+ function showDetails() {
1141
+ const results = calculationResults;
1142
+
1143
+ // Update detail values
1144
+ const detailCA = getElement('detailCA');
1145
+ const detailSocial = getElement('detailSocial');
1146
+ const detailVariable = getElement('detailVariable');
1147
+ const detailInsurance = getElement('detailInsurance');
1148
+ const detailPayment = getElement('detailPayment');
1149
+ const detailProfit = getElement('detailProfit');
1150
+ const detailHourly = getElement('detailHourly');
1151
+
1152
+ if (detailCA) detailCA.textContent = formatMoney(results.caTotal);
1153
+ if (detailSocial) detailSocial.textContent = '-' + formatMoney(results.socialCharges);
1154
+ if (detailVariable) detailVariable.textContent = '-' + formatMoney(results.variableCost);
1155
+ if (detailInsurance) detailInsurance.textContent = '-' + formatMoney(results.insuranceFixed);
1156
+ if (detailPayment) detailPayment.textContent = '-' + formatMoney(results.vehiclePayment);
1157
+ if (detailProfit) detailProfit.textContent = formatMoney(results.profit);
1158
+ if (detailHourly) detailHourly.textContent = formatMoney(results.hourlyRate) + '/h';
1159
+
1160
+ // Hide result, show details
1161
+ const resultScreen = getElement('resultScreen');
1162
+ if (resultScreen) resultScreen.classList.remove('active');
1163
+ if (detailsSection) detailsSection.classList.add('active');
1164
+ }
1165
+
1166
+ function hideDetails() {
1167
+ if (detailsSection) detailsSection.classList.remove('active');
1168
+ const resultScreen = getElement('resultScreen');
1169
+ if (resultScreen) resultScreen.classList.add('active');
1170
+ }
1171
+
1172
+ if (btnShowDetails) {
1173
+ btnShowDetails.addEventListener('click', showDetails);
1174
+ }
1175
+
1176
+ if (btnBackFromDetails) {
1177
+ btnBackFromDetails.addEventListener('click', hideDetails);
1178
+ }
1179
+
1180
  // Discover score button
1181
  const btnDiscoverScore = getElement('btnDiscoverScore');
1182
  if (btnDiscoverScore) {
1183
  btnDiscoverScore.addEventListener('click', () => {
1184
+ // Hide details if open
1185
+ if (detailsSection) detailsSection.classList.remove('active');
1186
+
1187
  showScoreLoading();
1188
 
1189
  setTimeout(() => {
 
1193
  });
1194
  }
1195
 
1196
+ // Back button from score to results
1197
+ const btnBackFromScore = getElement('btnBackFromScore');
1198
+ if (btnBackFromScore) {
1199
+ btnBackFromScore.addEventListener('click', () => {
1200
+ const scoreScreen = getElement('scoreScreen');
1201
+ if (scoreScreen) scoreScreen.classList.remove('active');
1202
+ showResults();
1203
+ });
1204
+ }
1205
+
1206
  // Reset button
1207
  const btnReset = getElement('btnReset');
1208
  if (btnReset) {
 
1211
  formData = {
1212
  coursesPerDay: 0,
1213
  daysPerMonth: 0,
1214
+ hoursPerMonth: 0,
1215
+ distancePerMonth: 0,
1216
  platformRevenue: 0,
1217
  privateRevenue: 0,
1218
  tipsRevenue: 0,
1219
+ vehicleType: '',
1220
+ vehiclePayment: 0
1221
  };
1222
 
1223
  // Clear inputs
1224
  if (coursesPerDayInput) coursesPerDayInput.value = '';
1225
  if (daysPerMonthInput) daysPerMonthInput.value = '';
1226
+ if (hoursPerMonthInput) hoursPerMonthInput.value = '';
1227
+ if (distancePerMonthInput) distancePerMonthInput.value = '';
1228
  if (platformRevenueInput) platformRevenueInput.value = '';
1229
  if (privateRevenueInput) privateRevenueInput.value = '';
1230
  if (tipsRevenueInput) tipsRevenueInput.value = '';
1231
+ if (vehiclePaymentInput) vehiclePaymentInput.value = '';
1232
 
1233
  vehicleBtns.forEach(b => b.classList.remove('selected'));
1234
  if (vehicleTypeInput) vehicleTypeInput.value = '';
 
1236
 
1237
  if (totalCoursesPreview) totalCoursesPreview.textContent = '0';
1238
 
1239
+ // Hide details if open
1240
+ if (detailsSection) detailsSection.classList.remove('active');
1241
+
1242
  // Go back to step 1
1243
  showStep(1);
1244
  });
 
1255
 
1256
  if (daysPerMonthInput) {
1257
  daysPerMonthInput.addEventListener('keypress', (e) => {
1258
+ if (e.key === 'Enter') {
1259
+ if (hoursPerMonthInput) hoursPerMonthInput.focus();
1260
+ }
1261
+ });
1262
+ }
1263
+
1264
+ if (hoursPerMonthInput) {
1265
+ hoursPerMonthInput.addEventListener('keypress', (e) => {
1266
+ if (e.key === 'Enter') {
1267
+ if (distancePerMonthInput) distancePerMonthInput.focus();
1268
+ }
1269
+ });
1270
+ }
1271
+
1272
+ if (distancePerMonthInput) {
1273
+ distancePerMonthInput.addEventListener('keypress', (e) => {
1274
  if (e.key === 'Enter' && btnStep1Next) btnStep1Next.click();
1275
  });
1276
  }