Aller au contenu
Home » pcestim

pcestim

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 = «  » }) => (

{Icon && }

);

// — 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 (

0 ? 2.5 : 0}rem)` }}
/>

{[
{ 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 ? ‘bg-slate-800 border-blue-500/50 text-blue-400 scale-100’ : ‘bg-[#0f172a] border-slate-700/50 text-slate-600’
}`}>

))}

);
};

return (

{/* ========================================= */}
{/* INTERFACE UTILISATEUR (CACHÉE À L’IMPRESSION) */}
{/* ========================================= */}

SYS. COMPENSATOIRE

/ Module d’Analyse Juridique v2.4 /

Réseau Sécurisé

{renderStepIndicator()}

{/* CONTENEUR PRINCIPAL AVEC OVERFLOW HIDDEN */}

{/* WRAPPER DES ÉTAPES (POSITIONNEMENT RELATIF POUR LE SCROLL LATÉRAL) */}

{/* ETAPE 1 */}

Paramètres Initiaux & Chronologie

/ Identification Cibles /


handleUpdate(‘contexte’, ‘nomCreancier’, e.target.value)} placeholder= »Nom… » />
handleUpdate(‘contexte’, ‘nomDebiteur’, e.target.value)} placeholder= »Nom… » />

/ Contexte Familial /

handleUpdate(‘autres’, ‘nombreEnfants’, parseNum(e.target.value))} placeholder= »Ex: 2″ />
handleUpdate(‘autres’, ‘ageEnfants’, e.target.value)} placeholder= »Ex: 5, 12″ />

/ Chronologie Événementielle /

handleUpdate(‘dates’, ‘dateMariage’, e.target.value)} />
handleUpdate(‘dates’, ‘dateSeparation’, e.target.value)} />
handleUpdate(‘dates’, ‘dateDivorcePrevisible’, e.target.value)} extraClass= »col-span-2″ />
handleUpdate(‘dates’, ‘dateNaissanceCreancier’, e.target.value)} />
handleUpdate(‘dates’, ‘dateNaissanceDebiteur’, e.target.value)} />

{/* ETAPE 2 */}

Flux Financiers & Projections (T+8)

Sujet : Débiteur


handleRevenusUpdate(‘debiteur’, ‘revenusAvantImpots’, parseNum(e.target.value))} />
handleRevenusUpdate(‘debiteur’, ‘contributionEnfants’, parseNum(e.target.value))} />

Sujet : Créancier


handleRevenusUpdate(‘creancier’, ‘revenusAvantImpots’, parseNum(e.target.value))} />
handleRevenusUpdate(‘creancier’, ‘contributionEnfants’, parseNum(e.target.value))} />

{/* ETAPE 3 */}

Capitaux, Retraite & Variables

/ Patrimoine Non Prod /

handleUpdate(‘patrimoine’, ‘debiteur’, parseNum(e.target.value))} />
handleUpdate(‘patrimoine’, ‘creancier’, parseNum(e.target.value))} />
handleUpdate(‘patrimoine’, ‘tauxRendement’, parseNum(e.target.value))} />

/ Impact Carrière /

Correction mathématique des choix pro. durant l’union.

handleUpdate(‘retraite’, ‘revenuAvantCessationActivite’, parseNum(e.target.value))} />
handleUpdate(‘retraite’, ‘nbAnneesSansCotisations’, parseNum(e.target.value))} />

/ Variables Système /

handleUpdate(‘autres’, ‘pensionAlimentaire’, parseNum(e.target.value))} />

handleUpdate(‘autres’, ‘coefficientPonderation’, parseNum(e.target.value))} extraClass= »!text-yellow-400″ />

SYS_DEFAULT: 0.6.

{/* ETAPE 4 */}

{!computations.synthese.isEligible ? (

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 /

{computations.methodes.map((m, idx) => (

{m.name}

))}

{showIntermediates && (

1. Matrice Revenus (D / C)

REVENU_NET_ACTUEL: {formatCurrency(computations.intermediaires.netD)} / {formatCurrency(computations.intermediaires.netC)}
REV_MOYEN_8_ANS: {formatCurrency(computations.intermediaires.moyenPrevD)} / {formatCurrency(computations.intermediaires.moyenPrevC)}
REV_PATRIMOINE: {formatCurrency(computations.intermediaires.revPatD)} / {formatCurrency(computations.intermediaires.revPatC)}
REV_CORRIGÉ_FINAL: {formatCurrency(computations.intermediaires.corrigeD)} / {formatCurrency(computations.intermediaires.corrigeC)}
DIFFÉRENCE_MENSUELLE: {formatCurrency(computations.intermediaires.diffMensuelle)}

2. Coeffs & Distorsions temporelles

DURÉE_UNION_ANNÉES: {computations.intermediaires.dureeMariage.toFixed(2)}
ÂGE_CRÉANCIER: {computations.intermediaires.ageCreancier.toFixed(2)}
ÂGE_DÉBITEUR: {computations.intermediaires.ageDebiteur.toFixed(2)}
COEFFICIENT_ÂGE: {computations.intermediaires.coeffAge.toFixed(4)}
DISPARITÉ_LIÉE_DURÉE: {formatCurrency(computations.intermediaires.dispariteDuree)}
POINTS_DEPONDT: {computations.intermediaires.ptsAgeDepondt} + {computations.intermediaires.ptsDureeDepondt}

)}

)}

{/* BARRE DE NAVIGATION (FOOTER FIXE SOUS LE SCROLL) */}

{step < 4 ? (
) : (


)}

{/* ========================================= */}
{/* SECTION D’IMPRESSION RIGIDE (PDF SEULEMENT) */}
{/* ========================================= */}

);
}