import React, { useState, useMemo, useEffect } from ‘react’;
import {
Calculator, ChevronRight, ChevronLeft, Calendar,
Euro, User, Briefcase, TrendingUp, AlertCircle,
CheckCircle, FileText, ChevronDown, ChevronUp, Shield, Activity,
Printer, Download, Scale
} from ‘lucide-react’;
// — ÉTAT INITIAL VIERGE —
const initialFormData = {
contexte: {
monClientEst: « Créancier »,
nomCreancier: « »,
nomDebiteur: « »
},
dates: {
dateMariage: « »,
dateSeparation: « »,
dateDivorcePrevisible: « »,
dateNaissanceCreancier: « »,
dateNaissanceDebiteur: « »
},
revenus: {
debiteur: {
periodicite: « Mensuel »,
revenusAvantImpots: « »,
contributionEnfants: « »,
changement8Ans: false,
revenuChangement: « »,
contributionPrevisibleEnfants: « »,
datePrevisibleModif: « »
},
creancier: {
periodicite: « Mensuel »,
revenusAvantImpots: « »,
contributionEnfants: « »,
changement8Ans: false,
revenuChangement: « »,
contributionPrevisibleEnfants: « »,
datePrevisibleModif: « »
}
},
patrimoine: {
debiteur: « »,
creancier: « »,
tauxRendement: « »
},
retraite: {
revenuAvantCessationActivite: « »,
nbAnneesSansCotisations: « »
},
autres: {
pensionAlimentaire: « »,
coefficientPonderation: 0.6, // Constante métier par défaut
nombreEnfants: « », // Ajout pour le contexte
ageEnfants: « » // Ajout pour le contexte
}
};
// — UTILITAIRES DE CALCUL ET DATES —
const differenceInYears = (date1, date2) => {
if (!date1 || !date2) return 0;
const diffTime = Math.abs(new Date(date1) – new Date(date2));
return diffTime / (1000 * 60 * 60 * 24 * 365.25);
};
const differenceInMonths = (date1, date2) => {
if (!date1 || !date2) return 0;
const d1 = new Date(date1);
const d2 = new Date(date2);
let months = (d1.getFullYear() – d2.getFullYear()) * 12;
months -= d2.getMonth();
months += d1.getMonth();
return months <= 0 ? 0 : months;
};
const formatCurrency = (amount) => {
if (amount === null || isNaN(amount)) return ‘-‘;
return new Intl.NumberFormat(‘fr-FR’, { style: ‘currency’, currency: ‘EUR’, maximumFractionDigits: 0 }).format(amount);
};
const formatDate = (dateStr) => {
if (!dateStr) return ‘-‘;
const date = new Date(dateStr);
return date.toLocaleDateString(‘fr-FR’);
};
// Sécurise la saisie pour accepter des champs vides sans afficher de « 0 »
const parseNum = (val) => val === » ? » : Number(val);
// Sécurise les calculs mathématiques avec des champs potentiellement vides
const num = (val) => Number(val) || 0;
// — COMPOSANT D’ANIMATION DES NOMBRES —
const AnimatedNumber = ({ value, duration = 1200 }) => {
const [count, setCount] = useState(0);
useEffect(() => {
let startTime = null;
let animationFrame;
const animate = (timestamp) => {
if (!startTime) startTime = timestamp;
const progress = Math.min((timestamp – startTime) / duration, 1);
const easeProgress = progress === 1 ? 1 : 1 – Math.pow(2, -10 * progress);
setCount(Math.floor(easeProgress * value));
if (progress < 1) {
animationFrame = requestAnimationFrame(animate);
} else {
setCount(value);
}
};
animationFrame = requestAnimationFrame(animate);
return () => cancelAnimationFrame(animationFrame);
}, [value, duration]);
return formatCurrency(count);
};
// — COMPOSANT CHAMP DE SAISIE —
const InputField = ({ label, type = « text », icon: Icon, value, onChange, placeholder, extraClass = « » }) => (
);
// — COMPOSANT PRINCIPAL —
export default function App() {
const [step, setStep] = useState(1);
const [showIntermediates, setShowIntermediates] = useState(false);
const [isExporting, setIsExporting] = useState(false);
const [pdfMode, setPdfMode] = useState(false);
const [formData, setFormData] = useState(initialFormData);
const handleUpdate = (section, field, value) => {
setFormData(prev => ({
…prev,
[section]: { …prev[section], [field]: value }
}));
};
const handleRevenusUpdate = (personne, field, value) => {
setFormData(prev => ({
…prev,
revenus: {
…prev.revenus,
[personne]: { …prev.revenus[personne], [field]: value }
}
}));
};
const handleReset = () => {
setFormData(initialFormData);
setStep(1);
window.scrollTo({ top: 0, behavior: ‘smooth’ });
};
// — LOGIQUE DE GÉNÉRATION PDF —
useEffect(() => {
if (pdfMode) {
const element = document.getElementById(‘print-section’);
const nomC = formData.contexte.nomCreancier || ‘C’;
const nomD = formData.contexte.nomDebiteur || ‘D’;
const opt = {
margin: [15, 15, 15, 15],
filename: `Estimation_PC_${nomC}_c_${nomD}.pdf`,
image: { type: ‘jpeg’, quality: 1 },
html2canvas: { scale: 2, useCORS: true, letterRendering: true, backgroundColor: ‘#ffffff’ },
jsPDF: { unit: ‘mm’, format: ‘a4’, orientation: ‘portrait’ },
pagebreak: { mode: [‘css’, ‘legacy’], avoid: [‘.avoid-break’, ‘tr’, ‘li’] }
};
const generate = () => {
setTimeout(() => {
window.html2pdf().set(opt).from(element).save().then(() => {
setPdfMode(false);
setIsExporting(false);
}).catch(err => {
console.error(« Erreur PDF: », err);
setPdfMode(false);
setIsExporting(false);
});
}, 500);
};
if (!window.html2pdf) {
const script = document.createElement(‘script’);
script.src = « https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js »;
script.onload = generate;
document.head.appendChild(script);
} else {
generate();
}
}
}, [pdfMode, formData]);
const handlePrint = () => {
setIsExporting(true);
setPdfMode(true);
};
// — MOTEUR DE CALCUL —
const computations = useMemo(() => {
const d = formData.dates;
const rD = formData.revenus.debiteur;
const rC = formData.revenus.creancier;
const p = formData.patrimoine;
const ret = formData.retraite;
const aut = formData.autres;
const refDate = d.dateDivorcePrevisible || d.dateSeparation || new Date().toISOString().split(‘T’)[0];
const revAnnuelD = rD.periodicite === « Mensuel » ? num(rD.revenusAvantImpots) * 12 : num(rD.revenusAvantImpots);
const revAnnuelC = rC.periodicite === « Mensuel » ? num(rC.revenusAvantImpots) * 12 : num(rC.revenusAvantImpots);
const revMensuelD = rD.periodicite === « Annuel » ? num(rD.revenusAvantImpots) / 12 : num(rD.revenusAvantImpots);
const revMensuelC = rC.periodicite === « Annuel » ? num(rC.revenusAvantImpots) / 12 : num(rC.revenusAvantImpots);
const netD = revMensuelD – num(rD.contributionEnfants);
const netC = revMensuelC – num(rC.contributionEnfants);
const prevNetD = num(rD.revenuChangement) – num(rD.contributionPrevisibleEnfants);
const prevNetC = num(rC.revenuChangement) – num(rC.contributionPrevisibleEnfants);
const calcMoyen8Ans = (personneData, netActuel, netPrevisible) => {
if (!personneData.changement8Ans || !personneData.datePrevisibleModif) return netActuel;
const moisAvantChangement = differenceInMonths(personneData.datePrevisibleModif, refDate);
if (moisAvantChangement >= 96) return netActuel;
if (moisAvantChangement <= 0) return netPrevisible;
return ((netActuel * moisAvantChangement) + (netPrevisible * (96 - moisAvantChangement))) / 96;
};
const moyenPrevD = calcMoyen8Ans(rD, netD, prevNetD);
const moyenPrevC = calcMoyen8Ans(rC, netC, prevNetC);
const revPatD = (num(p.debiteur) * num(p.tauxRendement)) / 100 / 12;
const revPatC = (num(p.creancier) * num(p.tauxRendement)) / 100 / 12;
const corrigeD = revPatD + moyenPrevD;
const corrigeC = revPatC + moyenPrevC;
const diffMensuelle = corrigeD - corrigeC;
const dureeMariage = differenceInYears(refDate, d.dateMariage);
const ageCreancier = differenceInYears(refDate, d.dateNaissanceCreancier);
const ageDebiteur = differenceInYears(refDate, d.dateNaissanceDebiteur);
const uniteDisparite = diffMensuelle * num(aut.coefficientPonderation);
const dispariteDuree = uniteDisparite * dureeMariage;
let coeffAge = 0;
if (ageCreancier < 62) {
coeffAge = (0.01 * ageCreancier) + 0.82;
} else {
coeffAge = (-0.01 * ageCreancier) + 2.06;
}
const dispariteAgeDuree = coeffAge * dispariteDuree;
const reparationRetraite = num(ret.revenuAvantCessationActivite) * num(ret.nbAnneesSansCotisations);
const capaciteEpargneCapitalD = 0.30 * 96 * moyenPrevD;
let ptsAgeDepondt = 1;
if (ageCreancier >= 30 && ageCreancier < 40) ptsAgeDepondt = 2;
else if (ageCreancier >= 40 && ageCreancier < 50) ptsAgeDepondt = 3;
else if (ageCreancier >= 50 && ageCreancier < 60) ptsAgeDepondt = 4;
else if (ageCreancier >= 60) ptsAgeDepondt = 5;
let ptsDureeDepondt = 5;
if (dureeMariage >= 5 && dureeMariage < 10) ptsDureeDepondt = 8;
else if (dureeMariage >= 10 && dureeMariage < 15) ptsDureeDepondt = 10;
else if (dureeMariage >= 15 && dureeMariage < 20) ptsDureeDepondt = 12;
else if (dureeMariage >= 20) ptsDureeDepondt = 15;
const isEligible = diffMensuelle > 0;
let pcEstim = isEligible ? Math.round(dispariteAgeDuree + reparationRetraite) : 0;
let pcTiers = isEligible ? Math.round((1/3) * (revAnnuelD – revAnnuelC) * (dureeMariage / 2)) : 0;
let pcVingtPourcent = isEligible ? Math.round(0.20 * (revAnnuelD – revAnnuelC) * 8) : 0;
let pcPension = (num(aut.pensionAlimentaire) > 0 && isEligible) ? Math.round(num(aut.pensionAlimentaire) * 12 * (dureeMariage / 2) / 2) : null;
let pcDepondt = isEligible ? Math.round(((revMensuelD – revMensuelC) / 2) * (ptsAgeDepondt + ptsDureeDepondt) * 3) : 0;
const allMethodsRaw = [
{ name: « Méthode PC Estim », value: pcEstim },
{ name: « Méthode du 1/3 », value: pcTiers },
{ name: « Méthode des 20% », value: pcVingtPourcent },
{ name: « Méthode Depondt », value: pcDepondt }
];
if (pcPension !== null && pcPension > 0) {
allMethodsRaw.push({ name: « Méthode Pension », value: pcPension });
}
const validMethods = allMethodsRaw.filter(m => m.value > 0);
const validValues = validMethods.map(m => m.value).sort((a, b) => a – b);
let moyenne = 0;
let moyenneTronquee = 0;
let ecartType = 0;
if (validValues.length > 0) {
moyenne = Math.round(validValues.reduce((a, b) => a + b, 0) / validValues.length);
if (validValues.length >= 3) {
const tronqueeArray = validValues.slice(1, -1);
moyenneTronquee = Math.round(tronqueeArray.reduce((a, b) => a + b, 0) / tronqueeArray.length);
} else {
moyenneTronquee = moyenne;
}
const variance = validValues.reduce((acc, val) => acc + Math.pow(val – moyenne, 2), 0) / validValues.length;
ecartType = Math.round(Math.sqrt(variance));
}
const mensualite8Ans = pcEstim / (12 * 8);
const capMensuelleMax = capaciteEpargneCapitalD / 96;
return {
intermediaires: {
revAnnuelD, revAnnuelC, revMensuelD, revMensuelC, netD, netC,
moyenPrevD, moyenPrevC, revPatD, revPatC, corrigeD, corrigeC,
diffMensuelle, dureeMariage, ageCreancier, ageDebiteur, coeffAge, dispariteDuree, dispariteAgeDuree,
reparationRetraite, capaciteEpargneCapitalD, ptsAgeDepondt, ptsDureeDepondt,
mensualite8Ans, capMensuelleMax
},
methodes: validMethods,
synthese: {
moyenne, moyenneTronquee, ecartType,
min: validValues.length > 0 ? validValues[0] : 0,
max: validValues.length > 0 ? validValues[validValues.length – 1] : 0,
isEligible, nbMethodes: validMethods.length
}
};
}, [formData]);
// — LOGIQUE DE TRANSITION ULTRA-SMOOTH —
const getStepClass = (stepNum) => {
const isActive = step === stepNum;
const isPast = step > stepNum;
return `w-full transition-all duration-700 ease-[cubic-bezier(0.22,1,0.36,1)] ${
isActive
? ‘relative opacity-100 translate-x-0 z-10 pointer-events-auto’
: `absolute top-0 left-0 opacity-0 z-0 pointer-events-none blur-md scale-[0.95] ${
isPast ? ‘-translate-x-20’ : ‘translate-x-20’
}`
}`;
};
// — RENDU DU HEADER —
const renderStepIndicator = () => {
const progress = ((step – 1) / 3) * 100;
return (
/>
{ num: 1, label: « CONTEXTE », icon: Calendar },
{ num: 2, label: « REVENUS », icon: Briefcase },
{ num: 3, label: « PATRIMOINE », icon: TrendingUp },
{ num: 4, label: « RÉSULTATS », icon: Activity }
].map((s) => (
}`}>
s.num ? ‘text-blue-400/70’ : ‘text-slate-600’
}`}>
{s.label}
))}
);
};
return (
{/* ========================================= */}
{/* INTERFACE UTILISATEUR (CACHÉE À L’IMPRESSION) */}
{/* ========================================= */}
SYS. COMPENSATOIRE
/ Module d’Analyse Juridique v2.4 /
{renderStepIndicator()}
{/* CONTENEUR PRINCIPAL AVEC OVERFLOW HIDDEN */}
{/* WRAPPER DES ÉTAPES (POSITIONNEMENT RELATIF POUR LE SCROLL LATÉRAL) */}
{/* ETAPE 1 */}
Paramètres Initiaux & Chronologie
/ Identification Cibles /
/ Contexte Familial /
/ Chronologie Événementielle /
{/* ETAPE 2 */}
Flux Financiers & Projections (T+8)
Sujet : Débiteur
{formData.revenus.debiteur.changement8Ans && (
)}
Sujet : Créancier
{formData.revenus.creancier.changement8Ans && (
)}
{/* ETAPE 3 */}
Capitaux, Retraite & Variables
/ Patrimoine Non Prod /
/ Impact Carrière /
/ Variables Système /
SYS_DEFAULT: 0.6.
{/* ETAPE 4 */}
Alerte : Disparité Nulle
Revenus corrigés du Créancier ≥ Débiteur (ou données manquantes pour calculer une disparité). Les conditions algorithmiques et juridiques pour déclencher la prestation ne sont pas remplies.
) : (
Output : Synthèse Multimodale
Val. Moyenne
Moyenne Tronquée *
Dispersion (σ)
±
Dataset : {computations.synthese.nbMethodes} algorithmes valides.
| Range global : [{formatCurrency(computations.synthese.min)} ; {formatCurrency(computations.synthese.max)}]
/ Modules de calcul individuels /
{m.name}
))}
{showIntermediates && (
1. Matrice Revenus (D / C)
2. Coeffs & Distorsions temporelles
)}
)}
{/* BARRE DE NAVIGATION (FOOTER FIXE SOUS LE SCROLL) */}
{step < 4 ? (
) : (
)}
{/* ========================================= */}
{/* SECTION D’IMPRESSION RIGIDE (PDF SEULEMENT) */}
{/* ========================================= */}
{/* En-tête du document */}
RAPPORT D’ESTIMATION
Prestation Compensatoire
{/* Bloc 1 : Synthèse des Résultats */}
1. SYNTHÈSE DES ESTIMATIONS
{!computations.synthese.isEligible ? (
) : (
<>
| Méthode de calcul utilisée | Montant estimé |
|---|---|
| {m.name} | {formatCurrency(m.value)} |
>
)}
{/* Bloc 2 : Données d’entrée */}
2. HYPOTHÈSES RETENUES
| Date de Mariage | {formatDate(formData.dates.dateMariage)} |
| Séparation | {formatDate(formData.dates.dateSeparation)} |
| Réf. Divorce | {formatDate(formData.dates.dateDivorcePrevisible)} |
| Durée retenue | {computations.intermediaires.dureeMariage.toFixed(1)} ans |
| Créancier | {formatDate(formData.dates.dateNaissanceCreancier)} ({computations.intermediaires.ageCreancier.toFixed(1)} ans) |
| Débiteur | {formatDate(formData.dates.dateNaissanceDebiteur)} ({computations.intermediaires.ageDebiteur.toFixed(1)} ans) |
| Client représenté | {formData.contexte.monClientEst} |
| Enfants communs | {formData.autres.nombreEnfants || « 0 »} {formData.autres.ageEnfants ? `(Âges: ${formData.autres.ageEnfants})` : »} |
| DONNÉES FINANCIÈRES | DÉBITEUR | CRÉANCIER |
|---|---|---|
| Revenus nets actuels / mois | {formatCurrency(computations.intermediaires.netD)} | {formatCurrency(computations.intermediaires.netC)} |
| Moyenne projetée (Horizon 8 ans) | {formatCurrency(computations.intermediaires.moyenPrevD)} | {formatCurrency(computations.intermediaires.moyenPrevC)} |
| Patrimoine (Non productif) | {formatCurrency(formData.patrimoine.debiteur)} | {formatCurrency(formData.patrimoine.creancier)} |
| REVENUS CORRIGÉS RETENUS / MOIS | {formatCurrency(computations.intermediaires.corrigeD)} | {formatCurrency(computations.intermediaires.corrigeC)} |
{/* Bloc 3 : Calculs Intermédiaires */}
{computations.synthese.isEligible && (
3. INDICATEURS DE CALCULS (TRANSPARENCE)
)}
{/* Footer Légal */}
);
}
