Amélioration de la fonctionnalité Fiscalité
This commit is contained in:
1 parent
5437091884
commit
5dc6f21e61
7 files changed
+1206
-189
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import PageIcon from '../components/PageIcon.jsx';
|
||||
import EmptyState from '../components/EmptyState.jsx';
|
||||
import Pagination from '../components/Pagination.jsx';
|
||||
@@ -10,6 +11,7 @@ import { fmtEUR, fmtStatut } from '../utils/format.js';
|
||||
import Cerfa2561Preview from '../components/Cerfa2561Preview.jsx';
|
||||
import CerfaRecapTable from '../components/CerfaRecapTable.jsx';
|
||||
import Cerfa2778Preview from '../components/Cerfa2778Preview.jsx';
|
||||
import Cerfa2047Preview from '../components/Cerfa2047Preview.jsx';
|
||||
import Cerfa2042Preview from '../components/Cerfa2042Preview.jsx';
|
||||
|
||||
/* ── YearSelector ────────────────────────────────────────────── */
|
||||
@@ -144,8 +146,78 @@ function ExportDropdown({ disabled, onCSV, onJSON }) {
|
||||
);
|
||||
}
|
||||
|
||||
/* ── Ligne d'explication d'un formulaire CERFA ── */
|
||||
function InfoLigneCerfa({ code, titre, texte }) {
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: 12, alignItems: 'flex-start' }}>
|
||||
<div style={{
|
||||
flexShrink: 0, minWidth: 62, textAlign: 'center',
|
||||
fontFamily: 'monospace', fontWeight: 800, fontSize: 'var(--fs-xs)',
|
||||
color: 'var(--primary)', background: 'rgba(99,102,241,0.07)',
|
||||
borderRadius: 6, padding: '4px 6px', letterSpacing: '.03em', whiteSpace: 'nowrap',
|
||||
}}>{code}</div>
|
||||
<div>
|
||||
<div style={{ fontSize: 'var(--fs-sm)', fontWeight: 600 }}>{titre}</div>
|
||||
<div style={{ fontSize: 'var(--fs-xs)', color: 'var(--text-muted)', marginTop: 2, lineHeight: 1.5 }}>{texte}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── Schéma : quel formulaire alimente quel formulaire ── */
|
||||
function CerfaFlowDiagram({ hasEtranger }) {
|
||||
const boxFill = 'var(--surface-2)';
|
||||
const box = (x, y, w, h, code, label, highlight) => (
|
||||
<g key={code}>
|
||||
<rect x={x} y={y} width={w} height={h} rx={10}
|
||||
fill={highlight ? 'rgba(99,102,241,0.08)' : boxFill}
|
||||
stroke={highlight ? 'var(--primary)' : 'var(--border)'} strokeWidth={highlight ? 1.8 : 1.3} />
|
||||
<text x={x + w / 2} y={y + h / 2 - 6} textAnchor="middle" fontFamily="monospace" fontWeight="800"
|
||||
fontSize="14" fill="var(--primary)">{code}</text>
|
||||
<text x={x + w / 2} y={y + h / 2 + 13} textAnchor="middle" fontSize="10" fill="var(--text-muted)">{label}</text>
|
||||
</g>
|
||||
);
|
||||
const arrow = (x1, y1, x2, y2, key) => (
|
||||
<line key={key} x1={x1} y1={y1} x2={x2} y2={y2}
|
||||
stroke="var(--text-muted)" strokeWidth={1.6} markerEnd="url(#cerfaFlowArrow)" />
|
||||
);
|
||||
const markerDef = (
|
||||
<defs>
|
||||
<marker id="cerfaFlowArrow" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto">
|
||||
<path d="M0,0 L6,3 L0,6 Z" fill="var(--text-muted)" />
|
||||
</marker>
|
||||
</defs>
|
||||
);
|
||||
|
||||
if (!hasEtranger) {
|
||||
return (
|
||||
<svg viewBox="0 0 420 80" style={{ width: '100%', maxWidth: 420, height: 'auto', display: 'block' }}>
|
||||
{markerDef}
|
||||
{arrow(160, 40, 228, 40, 'a1')}
|
||||
{box(20, 15, 140, 50, '2561', 'IFU')}
|
||||
{box(230, 15, 170, 50, '2042', 'Déclaration revenus', true)}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<svg viewBox="0 0 620 250" style={{ width: '100%', maxWidth: 560, height: 'auto', display: 'block' }}>
|
||||
{markerDef}
|
||||
{arrow(520, 58, 520, 98, 'a1')}
|
||||
{arrow(100, 148, 260, 190, 'a2')}
|
||||
{arrow(520, 148, 362, 190, 'a3')}
|
||||
|
||||
{box(440, 8, 160, 50, '2778-SD', 'PFO mensuel')}
|
||||
{box(20, 98, 160, 50, '2561', 'IFU')}
|
||||
{box(440, 98, 160, 50, '2047', 'Revenus étranger')}
|
||||
{box(210, 190, 200, 54, '2042', 'Déclaration revenus', true)}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── Composant principal ─────────────────────────────────────── */
|
||||
export default function TaxReport() {
|
||||
const navigate = useNavigate();
|
||||
const { activeId, activeView } = useInvestisseur();
|
||||
const { pfoAssujetti } = useUi();
|
||||
const [annee, setAnnee] = useState(String(new Date().getFullYear()));
|
||||
@@ -157,10 +229,14 @@ export default function TaxReport() {
|
||||
const [detailExpanded, setDetailExpanded] = useState(false);
|
||||
const [cerfaExpanded, setCerfaExpanded] = useState(false);
|
||||
const [hasPlateformes, setHasPlateformes] = useState(true); // true par défaut pour éviter un flash du mauvais message
|
||||
const [hasEtranger, setHasEtranger] = useState(false); // au moins une plateforme étrangère configurée (pour l'onglet CERFA 2047 — indépendant de l'année sélectionnée, même principe que le 2778-SD)
|
||||
|
||||
/* ── Plateformes configurées ? (pour distinguer "pas de plateforme" de "pas encore de données") ── */
|
||||
useEffect(() => {
|
||||
api.get('/plateformes').then(p => setHasPlateformes((p || []).length > 0)).catch(() => {});
|
||||
api.get('/plateformes').then(p => {
|
||||
setHasPlateformes((p || []).length > 0);
|
||||
setHasEtranger((p || []).some(pl => pl.domiciliation && pl.domiciliation !== 'FR'));
|
||||
}).catch(() => {});
|
||||
}, []);
|
||||
|
||||
/* ── Chargement des années disponibles ── */
|
||||
@@ -196,14 +272,14 @@ export default function TaxReport() {
|
||||
setData2042({
|
||||
case_2TT: frLignes.reduce((s, l) => s + (l.case_2TT ?? 0), 0),
|
||||
case_2TR: frLignes.reduce((s, l) => s + (l.case_2TR ?? 0), 0) + Math.round(platEtr.reduce((s, p) => s + etrBA(p), 0)),
|
||||
case_2BH: frLignes.reduce((s, l) => s + (l.case_2BH ?? 0), 0) + Math.round(platEtr.reduce((s, p) => s + etrBA(p), 0)),
|
||||
case_2CK: frLignes.reduce((s, l) => s + (l.case_2CK ?? 0), 0) + Math.round(platEtr.reduce((s, p) => s + etrBA(p), 0) * pfo),
|
||||
case_2BH: frLignes.reduce((s, l) => s + (l.case_2BH ?? 0), 0) + (pfoAssujetti ? Math.round(platEtr.reduce((s, p) => s + etrBA(p), 0)) : 0),
|
||||
case_2CK: frLignes.reduce((s, l) => s + (l.case_2CK ?? 0), 0) + (pfoAssujetti ? Math.round(platEtr.reduce((s, p) => s + etrBA(p), 0) * pfo) : 0),
|
||||
case_2TY: frLignes.reduce((s, l) => s + (l.case_2TY ?? 0), 0),
|
||||
});
|
||||
}).finally(() => setLoading(false));
|
||||
};
|
||||
|
||||
useEffect(load, [activeId, activeView, annee]); // eslint-disable-line
|
||||
useEffect(load, [activeId, activeView, annee, pfoAssujetti]); // eslint-disable-line
|
||||
|
||||
/* ── Pagination détail ── */
|
||||
const detail = data?.detail ?? [];
|
||||
@@ -301,10 +377,73 @@ export default function TaxReport() {
|
||||
</p>
|
||||
</div>}
|
||||
|
||||
{!detailExpanded && !cerfaExpanded && data2042 && (
|
||||
<div className="card" style={{ marginTop: 16 }}>
|
||||
<h3 style={{ marginTop: 0, marginBottom: 10 }}>À quoi correspond chaque CERFA ?</h3>
|
||||
<div style={{ display: 'flex', gap: 28, alignItems: 'center', flexWrap: 'wrap' }}>
|
||||
<div style={{ flex: '1 1 320px', minWidth: 280, display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
<InfoLigneCerfa
|
||||
code="2042"
|
||||
titre="Déclaration des revenus"
|
||||
texte="La déclaration principale : reprend les cases calculées ci-dessus (2TT, 2TR, 2BH, 2CK, 2TY)."
|
||||
/>
|
||||
<InfoLigneCerfa
|
||||
code="2561"
|
||||
titre="IFU — Imprimé fiscal unique"
|
||||
texte="Récapitulatif annuel transmis par les plateformes françaises soumises au prélèvement forfaitaire unique (PFU)."
|
||||
/>
|
||||
{hasEtranger && (
|
||||
<InfoLigneCerfa
|
||||
code="2047"
|
||||
titre="Revenus encaissés à l'étranger"
|
||||
texte="Obligatoire dès qu'une ou plusieurs de vos plateformes sont domiciliées hors de France — détaille les intérêts perçus pays par pays et le crédit d'impôt associé."
|
||||
/>
|
||||
)}
|
||||
{hasEtranger && (
|
||||
<InfoLigneCerfa
|
||||
code="2778-SD"
|
||||
titre="Prélèvement forfaitaire obligatoire (PFO) - selon votre situation fiscale"
|
||||
texte="Les intérêts perçus via des plateformes étrangères sont soumis à un prélèvement forfaitaire obligatoire (acompte) si vous avez un revenu fiscal de référence de l'avant-dernière année de 25 000 € pour un personne seule ou 50 000 € pour couple marié ou pacsé. Malgré la déclaration mensuelle aux autorités fiscales, la déclaration annuelle au travers du CERFA 2047 reste obligatoire."
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ flex: '1 1 320px', minWidth: 280, display: 'flex', justifyContent: 'center' }}>
|
||||
<CerfaFlowDiagram hasEtranger={hasEtranger} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{hasEtranger && (
|
||||
<div style={{
|
||||
marginTop: 14, padding: '12px 14px', borderRadius: 10,
|
||||
background: pfoAssujetti ? 'rgba(16,185,129,0.06)' : 'rgba(245,158,11,0.08)',
|
||||
border: `1px solid ${pfoAssujetti ? 'rgba(16,185,129,0.2)' : 'rgba(245,158,11,0.25)'}`,
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 16, flexWrap: 'wrap',
|
||||
}}>
|
||||
<div style={{ fontSize: 'var(--fs-sm)', lineHeight: 1.5 }}>
|
||||
{pfoAssujetti ? (
|
||||
<>Vous avez des plateformes étrangères et l'option de déclaration <strong>2778-SD</strong> est activée.</>
|
||||
) : (
|
||||
<>Vous avez des plateformes étrangères. Vérifiez si la déclaration mensuelle <strong>2778-SD</strong> s'applique à votre situation.</>
|
||||
)}
|
||||
</div>
|
||||
<button className="btn btn-sm btn-primary" onClick={() => navigate('/settings?section=ma-fiscalite')}>
|
||||
{pfoAssujetti ? 'Gérer dans les paramètres' : 'Vérifier dans les paramètres'}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className="text-muted" style={{ marginTop: 14, marginBottom: 0, fontSize: 12, lineHeight: 1.6 }}>
|
||||
⚠ Ce module est une aide à la préparation de votre déclaration ; il ne la remplace pas. Vérifiez chaque montant avant de le reporter.
|
||||
Vous restez seul responsable de l'exactitude de votre déclaration fiscale — Crowdlending ne saurait être tenu responsable des données déclarées.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!detailExpanded && !cerfaExpanded && (
|
||||
<div className="dr-tabs">
|
||||
<button className={`dr-tab${activeTab === '2042' ? ' active' : ''}`} onClick={() => setActiveTab('2042')}>CERFA 2042</button>
|
||||
<button className={`dr-tab${activeTab === 'cerfa' ? ' active' : ''}`} onClick={() => setActiveTab('cerfa')}>CERFA 2561 (IFU)</button>
|
||||
{hasEtranger && <button className={`dr-tab${activeTab === '2047' ? ' active' : ''}`} onClick={() => setActiveTab('2047')}>CERFA 2047</button>}
|
||||
{pfoAssujetti && <button className={`dr-tab${activeTab === '2778' ? ' active' : ''}`} onClick={() => setActiveTab('2778')}>CERFA 2778-SD</button>}
|
||||
</div>
|
||||
)}
|
||||
@@ -324,6 +463,10 @@ export default function TaxReport() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeTab === '2047' && hasEtranger && !detailExpanded && !cerfaExpanded && (
|
||||
<Cerfa2047Preview annee={annee} activeView={activeView} />
|
||||
)}
|
||||
|
||||
{activeTab === '2778' && pfoAssujetti && !detailExpanded && !cerfaExpanded && (
|
||||
<Cerfa2778Preview annee={annee} activeView={activeView} />
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user