448 lines
23 KiB
React
448 lines
23 KiB
React
import { useEffect, useState } from 'react';
|
||
import { api } from '../api.js';
|
||
import { useUi } from '../context/UiContext.jsx';
|
||
import {
|
||
SuiviMensuelMatrix, Report2042Block,
|
||
computeCases, getRatesForYear, MOIS_NUMS, r,
|
||
} from './Cerfa2778Preview.jsx';
|
||
|
||
const fmtPct = (v, nonPlafonne) => v == null
|
||
? (nonPlafonne
|
||
? <span title="Convention active sans taux forfaitaire chiffré : crédit égal à l'impôt réellement prélevé, sans plafond" style={{ color: 'var(--text-muted)', fontStyle: 'italic' }}>Non plafonné</span>
|
||
: <span style={{ color: 'var(--text-muted)' }}>—</span>)
|
||
: `${Number(v).toLocaleString('fr-FR', { minimumFractionDigits: 1, maximumFractionDigits: 1 })} %`;
|
||
|
||
// Cases CERFA 2047 : montants arrondis à l'euro le plus proche (règle fiscale), sans décimales —
|
||
// contrairement au Suivi mensuel (intermédiaire, non déclaré tel quel) qui garde 2 décimales.
|
||
const fmt0 = v => {
|
||
if (v == null || v === 0) return <span style={{ color: 'var(--text-muted)' }}>- €</span>;
|
||
return `${Number(v).toLocaleString('fr-FR', { minimumFractionDigits: 0, maximumFractionDigits: 0 })} €`;
|
||
};
|
||
|
||
/* ── Badge de case CERFA — même habillage que Case2042Row / Case2042 (Report 2042) ── */
|
||
function CaseBadge({ code, tone }) {
|
||
const success = tone === 'success';
|
||
return (
|
||
<span style={{
|
||
display: 'inline-block', fontFamily: 'monospace', fontWeight: 800, fontSize: 'var(--fs-xs)',
|
||
color: success ? 'var(--success)' : 'var(--primary)',
|
||
background: success ? 'rgba(16,185,129,0.12)' : 'rgba(99,102,241,0.07)',
|
||
borderRadius: 6, padding: '2px 6px', letterSpacing: '.04em', whiteSpace: 'nowrap',
|
||
}}>{code}</span>
|
||
);
|
||
}
|
||
function CaseHeader({ code, label, tone }) {
|
||
return (
|
||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4 }}>
|
||
<CaseBadge code={code} tone={tone} />
|
||
<span>{label}</span>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
/* ── Composant principal ── */
|
||
export default function Cerfa2047Preview({ annee, activeView }) {
|
||
const LS_KEY = 'cl_2778_excluded_plats'; // partagé avec le 2778-SD (mêmes plateformes étrangères)
|
||
const { pfoAssujetti } = useUi();
|
||
|
||
const [data2778, setData2778] = useState(null);
|
||
const [data2047, setData2047] = useState(null);
|
||
const [loading, setLoading] = useState(false);
|
||
const [pfuList, setPfuList] = useState([]);
|
||
const [excluded, setExcluded] = useState(() => {
|
||
try { return new Set(JSON.parse(localStorage.getItem(LS_KEY)) ?? []); }
|
||
catch { return new Set(); }
|
||
});
|
||
const [view, setView] = useState('matrice'); // 'matrice' | 'donnees' | 'report2042'
|
||
|
||
useEffect(() => {
|
||
setLoading(true);
|
||
setData2778(null);
|
||
setData2047(null);
|
||
const scopeParams = activeView === 'all' ? { scope: 'all' } : {};
|
||
Promise.all([
|
||
api.get('/taxreport/2778', { annee, ...scopeParams }),
|
||
api.get('/taxreport/2047', { annee, ...scopeParams }),
|
||
pfuList.length === 0 ? api.get('/pfu') : Promise.resolve(null),
|
||
]).then(([d2778, d2047, pfu]) => {
|
||
setData2778(d2778);
|
||
setData2047(d2047);
|
||
if (pfu) setPfuList(pfu);
|
||
}).finally(() => setLoading(false));
|
||
}, [annee, activeView]); // eslint-disable-line
|
||
|
||
if (loading || !data2778 || !data2047) return <div className="card text-muted">Chargement…</div>;
|
||
|
||
const plateformes = data2778.plateformes;
|
||
const rates = getRatesForYear(annee, pfuList);
|
||
const TOTAL_TAX = rates.pfo + rates.csg + rates.crds + rates.solidarite;
|
||
|
||
if (plateformes.length === 0) {
|
||
return (
|
||
<div className="card">
|
||
<p className="text-muted" style={{ margin: 0 }}>
|
||
Aucun revenu de plateforme étrangère pour {annee}.
|
||
</p>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
const togglePlat = (id) => {
|
||
setExcluded(prev => {
|
||
const next = new Set(prev);
|
||
if (next.has(id)) next.delete(id); else next.add(id);
|
||
localStorage.setItem(LS_KEY, JSON.stringify([...next]));
|
||
return next;
|
||
});
|
||
};
|
||
|
||
const totauxMois = MOIS_NUMS.map(m => {
|
||
let sum = 0;
|
||
for (const plat of plateformes) {
|
||
if (!excluded.has(plat.id)) sum += plat.mois[m] ?? 0;
|
||
}
|
||
return r(sum);
|
||
});
|
||
const totalBA = r(totauxMois.reduce((a, b) => a + b, 0));
|
||
const totalIA = computeCases(totalBA, rates).IA;
|
||
|
||
// Lignes 2047 filtrées sur les plateformes non exclues (cohérence avec le Suivi mensuel)
|
||
const lignes2047 = data2047.lignes.filter(l => !excluded.has(`${l.plateforme_id}_${l.investisseur_id}`));
|
||
const lignesEligibles = lignes2047.filter(l => l.eligible_ci);
|
||
const lignesNonEligibles = lignes2047.filter(l => !l.eligible_ci);
|
||
// Regroupement par pays pour faciliter le report dans la déclaration 2047
|
||
// (le formulaire officiel attend une ligne par pays, pas par plateforme)
|
||
const groupesEligibles = Object.values(
|
||
lignesEligibles.reduce((acc, l) => {
|
||
const key = l.domiciliation || l.pays_nom || '?';
|
||
if (!acc[key]) acc[key] = { pays_nom: l.pays_nom, domiciliation: l.domiciliation, lignes: [] };
|
||
acc[key].lignes.push(l);
|
||
return acc;
|
||
}, {})
|
||
).sort((a, b) => (a.pays_nom ?? '').localeCompare(b.pays_nom ?? ''));
|
||
const t = lignes2047.reduce((acc, l) => ({
|
||
case_233: acc.case_233 + l.case_233,
|
||
case_236: acc.case_236 + l.case_236,
|
||
case_237: acc.case_237 + l.case_237,
|
||
case_238: acc.case_238 + l.case_238,
|
||
case_250: acc.case_250 + l.case_250,
|
||
}), { case_233: 0, case_236: 0, case_237: 0, case_238: 0, case_250: 0 });
|
||
t.case_251 = r(t.case_238 + t.case_250);
|
||
t.case_8VL = r(t.case_237);
|
||
t.case_8PL = r(t.case_233);
|
||
|
||
const platLabel = (l) => {
|
||
const n = l.investisseur_nom ?? '';
|
||
const p = l.investisseur_prenom ?? '';
|
||
const who = p && n.startsWith(p) ? n : [p, n].filter(Boolean).join(' ');
|
||
return (
|
||
<>
|
||
<span style={{ fontWeight: 500 }}>{l.plateforme_nom}</span>
|
||
{who && <span style={{ fontSize: 'var(--fs-xs)', color: 'var(--text-muted)', marginLeft: 6 }}>— {who}</span>}
|
||
</>
|
||
);
|
||
};
|
||
|
||
const cellStyle = {
|
||
textAlign: 'right', padding: '7px 10px', fontSize: 'var(--fs-sm)',
|
||
whiteSpace: 'nowrap', borderBottom: '1px solid var(--border)',
|
||
};
|
||
const headStyle = {
|
||
textAlign: 'center', padding: '7px 10px', fontSize: 'var(--fs-xs)', fontWeight: 600,
|
||
color: 'var(--text-muted)', background: 'var(--surface-2)',
|
||
borderBottom: '1px solid var(--border)', whiteSpace: 'nowrap',
|
||
};
|
||
|
||
return (
|
||
<div className="card">
|
||
<div style={{ display: 'flex', gap: 0, alignItems: 'flex-start' }}>
|
||
<nav style={{
|
||
width: 200, flexShrink: 0, marginRight: 20,
|
||
background: 'var(--surface)', border: '1px solid var(--border)',
|
||
borderRadius: 10, padding: '8px 0', boxSizing: 'border-box',
|
||
}}>
|
||
<div style={{ padding: '6px 12px 8px', fontSize: 'var(--fs-xs)', fontWeight: 600, color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '.06em' }}>
|
||
Sections
|
||
</div>
|
||
<button
|
||
className={`account-nav-item${view === 'matrice' ? ' active' : ''}`}
|
||
onClick={() => setView('matrice')}
|
||
>
|
||
<svg width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='currentColor' strokeWidth='1.8' strokeLinecap='round' strokeLinejoin='round'>
|
||
<path d='M3 3h18v2H3V3zm0 7h12v2H3v-2zm0 7h18v2H3v-2z'/>
|
||
</svg>
|
||
Suivi mensuel
|
||
</button>
|
||
<button
|
||
className={`account-nav-item${view === 'donnees' ? ' active' : ''}`}
|
||
onClick={() => setView('donnees')}
|
||
>
|
||
<svg width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='currentColor' strokeWidth='1.8' strokeLinecap='round' strokeLinejoin='round'>
|
||
<path d='M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z'/>
|
||
</svg>
|
||
Données 2047
|
||
</button>
|
||
<button
|
||
className={`account-nav-item${view === 'report2042' ? ' active' : ''}`}
|
||
onClick={() => setView('report2042')}
|
||
>
|
||
<svg width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='currentColor' strokeWidth='1.8' strokeLinecap='round' strokeLinejoin='round'>
|
||
<path d='M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z'/>
|
||
</svg>
|
||
Report 2042
|
||
</button>
|
||
<div style={{ margin: '10px 12px 6px', borderTop: '1px solid var(--border)' }} />
|
||
<div style={{ padding: '4px 12px', fontSize: 'var(--fs-xs)', color: 'var(--text-muted)', lineHeight: 1.5 }}>
|
||
Plateformes non-françaises<br/>Cadre 2, rubrique 230 — Intérêts
|
||
</div>
|
||
</nav>
|
||
|
||
<div style={{ flex: 1, minWidth: 0 }}>
|
||
|
||
{/* ── Vue Matrice (partagée avec le 2778-SD) ── */}
|
||
{view === 'matrice' && (
|
||
<SuiviMensuelMatrix
|
||
annee={annee}
|
||
plateformes={plateformes}
|
||
excluded={excluded}
|
||
togglePlat={togglePlat}
|
||
totauxMois={totauxMois}
|
||
rates={rates}
|
||
TOTAL_TAX={TOTAL_TAX}
|
||
/>
|
||
)}
|
||
|
||
{/* ── Vue Données 2047 ── */}
|
||
{view === 'donnees' && (
|
||
<div className="card" style={{ padding: 0, overflow: 'auto' }}>
|
||
<div style={{ padding: '14px 14px 10px' }}>
|
||
<h4 style={{ margin: 0, fontWeight: 700 }}>Formulaire 2047 — Rubrique 230 (Intérêts)</h4>
|
||
<span style={{ fontSize: 'var(--fs-xs)', color: 'var(--text-muted)' }}>Vision annuelle {annee} — plateformes étrangères</span>
|
||
</div>
|
||
|
||
{/* Lignes 231-238 : éligibles au crédit d'impôt */}
|
||
<div style={{ padding: '0 14px 6px' }}>
|
||
<div style={{ fontSize: 'var(--fs-sm)', fontWeight: 600, marginBottom: 6, display: 'flex', alignItems: 'center', gap: 8 }}>
|
||
<CaseBadge code="231-238" /> Intérêts ouvrant droit à un crédit d'impôt égal à l'impôt payé à l'étranger
|
||
</div>
|
||
</div>
|
||
{lignesEligibles.length === 0 ? (
|
||
<div style={{ padding: '4px 14px 14px', color: 'var(--text-muted)', fontSize: 'var(--fs-sm)' }}>Aucune plateforme éligible pour {annee}.</div>
|
||
) : (
|
||
<table style={{ width: '100%', borderCollapse: 'collapse', minWidth: 900 }}>
|
||
<thead>
|
||
<tr>
|
||
<th style={{ ...headStyle, textAlign: 'left' }}>Plateforme — Détenteur</th>
|
||
<th style={headStyle}>Pays</th>
|
||
<th style={headStyle}><CaseHeader code="233" label="Montant net encaissé" /></th>
|
||
<th style={headStyle}><CaseHeader code="234" label="Taux applicable" /></th>
|
||
<th style={headStyle}><CaseHeader code="235" label="Résultat" /></th>
|
||
<th style={headStyle}><CaseHeader code="236" label="Impôt supporté à l'étranger" /></th>
|
||
<th style={{ ...headStyle, background: 'rgba(16,185,129,0.10)' }}><CaseHeader code="237" label="Crédit d'impôt retenu" tone="success" /></th>
|
||
<th style={headStyle}><CaseHeader code="238" label="Intérêts crédit d'impôt inclus" /></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{groupesEligibles.flatMap(g => {
|
||
const sub = g.lignes.reduce((acc, l) => ({
|
||
case_233: acc.case_233 + l.case_233,
|
||
case_235: acc.case_235 + l.case_235,
|
||
case_236: acc.case_236 + l.case_236,
|
||
case_237: acc.case_237 + l.case_237,
|
||
case_238: acc.case_238 + l.case_238,
|
||
}), { case_233: 0, case_235: 0, case_236: 0, case_237: 0, case_238: 0 });
|
||
const rows = g.lignes.map(l => (
|
||
<tr key={`${l.plateforme_id}_${l.investisseur_id}`}>
|
||
<td style={{ ...cellStyle, textAlign: 'left' }}>{platLabel(l)}</td>
|
||
<td style={{ ...cellStyle, textAlign: 'center' }}>{l.pays_nom}</td>
|
||
<td style={cellStyle}>{fmt0(l.case_233)}</td>
|
||
<td style={{ ...cellStyle, textAlign: 'center' }}>{fmtPct(l.case_234, !l.taux_plafonne)}</td>
|
||
<td style={cellStyle}>{fmt0(l.case_235)}</td>
|
||
<td style={cellStyle}>{fmt0(l.case_236)}</td>
|
||
<td style={{ ...cellStyle, fontWeight: 600, color: 'var(--success)', background: 'rgba(16,185,129,0.05)' }}>{fmt0(l.case_237)}</td>
|
||
<td style={{ ...cellStyle, fontWeight: 600 }}>{fmt0(l.case_238)}</td>
|
||
</tr>
|
||
));
|
||
if (g.lignes.length > 1) {
|
||
rows.push(
|
||
<tr key={`sous-total-${g.domiciliation}`} style={{ background: 'rgba(99,102,241,0.06)' }}>
|
||
<td style={{ ...cellStyle, textAlign: 'left', fontStyle: 'italic', color: 'var(--text-muted)' }} colSpan={2}>
|
||
Sous-total {g.pays_nom} ({g.lignes.length} plateformes)
|
||
</td>
|
||
<td style={{ ...cellStyle, fontWeight: 700 }}>{fmt0(sub.case_233)}</td>
|
||
<td style={cellStyle}></td>
|
||
<td style={{ ...cellStyle, fontWeight: 700 }}>{fmt0(sub.case_235)}</td>
|
||
<td style={{ ...cellStyle, fontWeight: 700 }}>{fmt0(sub.case_236)}</td>
|
||
<td style={{ ...cellStyle, fontWeight: 700, color: 'var(--success)' }}>{fmt0(sub.case_237)}</td>
|
||
<td style={{ ...cellStyle, fontWeight: 700 }}>{fmt0(sub.case_238)}</td>
|
||
</tr>
|
||
);
|
||
}
|
||
return rows;
|
||
})}
|
||
<tr style={{ background: 'var(--surface-2)' }}>
|
||
<td style={{ ...cellStyle, textAlign: 'left', fontWeight: 700 }} colSpan={2}>Total</td>
|
||
<td style={{ ...cellStyle, fontWeight: 700 }}>{fmt0(t.case_233)}</td>
|
||
<td style={cellStyle}></td>
|
||
<td style={cellStyle}></td>
|
||
<td style={{ ...cellStyle, fontWeight: 700 }}>{fmt0(t.case_236)}</td>
|
||
<td style={{ ...cellStyle, fontWeight: 700, color: 'var(--success)' }}>{fmt0(t.case_237)}</td>
|
||
<td style={{ ...cellStyle, fontWeight: 700 }}>{fmt0(t.case_238)}</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
)}
|
||
|
||
{/* Ligne 250 : n'ouvrant pas droit à crédit d'impôt */}
|
||
<div style={{ padding: '18px 14px 6px', borderTop: '1px solid var(--border)' }}>
|
||
<div style={{ fontSize: 'var(--fs-sm)', fontWeight: 600, marginBottom: 6, display: 'flex', alignItems: 'center', gap: 8 }}>
|
||
<CaseBadge code="250" /> Intérêts n'ouvrant pas droit à crédit d'impôt
|
||
</div>
|
||
</div>
|
||
{lignesNonEligibles.length === 0 ? (
|
||
<div style={{ padding: '4px 14px 14px', color: 'var(--text-muted)', fontSize: 'var(--fs-sm)' }}>Aucune plateforme dans ce cas pour {annee}.</div>
|
||
) : (
|
||
<table style={{ width: '100%', borderCollapse: 'collapse', minWidth: 900 }}>
|
||
<thead>
|
||
<tr>
|
||
<th style={{ ...headStyle, textAlign: 'left' }}>Plateforme — Détenteur</th>
|
||
<th style={headStyle}>Pays</th>
|
||
<th style={{ ...headStyle, textAlign: 'left' }}>Motif</th>
|
||
<th style={headStyle}><CaseHeader code="250" label="Montant" /></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{lignesNonEligibles.map(l => (
|
||
<tr key={`${l.plateforme_id}_${l.investisseur_id}`}>
|
||
<td style={{ ...cellStyle, textAlign: 'left' }}>{platLabel(l)}</td>
|
||
<td style={{ ...cellStyle, textAlign: 'center' }}>{l.domiciliation}</td>
|
||
<td style={{ ...cellStyle, textAlign: 'left', color: 'var(--text-muted)', fontSize: 'var(--fs-xs)' }}>{l.motif}</td>
|
||
<td style={cellStyle}>{fmt0(l.case_250)}</td>
|
||
</tr>
|
||
))}
|
||
<tr style={{ background: 'var(--surface-2)' }}>
|
||
<td style={{ ...cellStyle, textAlign: 'left', fontWeight: 700 }} colSpan={3}>Total</td>
|
||
<td style={{ ...cellStyle, fontWeight: 700 }}>{fmt0(t.case_250)}</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
)}
|
||
|
||
{/* Totaux 251 / 252 */}
|
||
<div style={{
|
||
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
|
||
padding: '12px 14px', marginTop: 4, borderTop: '2px solid var(--border)',
|
||
background: 'rgba(99,102,241,0.05)',
|
||
}}>
|
||
<div>
|
||
<div style={{ fontWeight: 700, fontSize: 'var(--fs-sm)', display: 'flex', alignItems: 'center', gap: 8 }}>
|
||
<CaseBadge code="251" /> Total des intérêts imposables (238 + 250)
|
||
</div>
|
||
<div style={{ fontSize: 'var(--fs-xs)', color: 'var(--text-muted)', marginTop: 4, display: 'flex', alignItems: 'center', gap: 8 }}>
|
||
<CaseBadge code="252" /> dont intérêts et autres produits de placement à revenu fixe → reporté en case <strong>2TR</strong> de la 2042 (voir onglet « Report 2042 »)
|
||
</div>
|
||
</div>
|
||
<div style={{ fontWeight: 800, fontSize: '1.2rem' }}>{fmt0(t.case_251)}</div>
|
||
</div>
|
||
|
||
<div style={{ padding: '10px 14px 14px', fontSize: 'var(--fs-xs)', color: 'var(--text-muted)', lineHeight: 1.6 }}>
|
||
L'éligibilité au crédit d'impôt (231-238 vs 250) est déterminée automatiquement à partir du pays de domiciliation
|
||
de chaque plateforme et du référentiel <strong>« 2047 — Taux crédit d'impôts »</strong> (Admin > Fiscalité).
|
||
Le crédit d'impôt retenu (237) est plafonné au plus petit des deux montants : taux conventionnel × montant net (235),
|
||
ou impôt réellement prélevé à l'étranger (236).
|
||
</div>
|
||
|
||
{/* Rubrique 9 : revenus déjà soumis aux prélèvements sociaux (2778-SD) */}
|
||
<div style={{ padding: '18px 14px 6px', borderTop: '1px solid var(--border)' }}>
|
||
<div style={{ fontSize: 'var(--fs-sm)', fontWeight: 600, marginBottom: 6, display: 'flex', alignItems: 'center', gap: 8 }}>
|
||
<CaseBadge code="272-273" /> Rubrique 9 — Revenus déjà soumis aux prélèvements sociaux (2778-SD)
|
||
</div>
|
||
</div>
|
||
{pfoAssujetti ? (
|
||
<div style={{ padding: '0 14px 14px' }}>
|
||
<div style={{ border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden' }}>
|
||
<Case2042Row
|
||
code="272"
|
||
label="Revenus déjà soumis aux prélèvements sociaux (avec CSG déductible si option barème)"
|
||
note="Même montant que la case 2BH — intérêts bruts totaux avant retenue locale, déclarés via vos 2778-SD"
|
||
value={totalBA}
|
||
/>
|
||
<Case2042Row
|
||
code="273"
|
||
label="Prélèvement forfaitaire non libératoire déjà versé"
|
||
note="Même montant que la case 2CK — somme des acomptes PFO (case IA) versés via vos 2778-SD"
|
||
value={totalIA}
|
||
/>
|
||
</div>
|
||
</div>
|
||
) : (
|
||
<div style={{
|
||
margin: '0 14px 14px', padding: '10px 14px', borderRadius: 8,
|
||
background: 'var(--surface-2)', fontSize: 'var(--fs-xs)', color: 'var(--text-muted)', lineHeight: 1.6,
|
||
}}>
|
||
Les cases 272 et 273 ne s'appliquent que si l'option de déclaration 2778-SD est activée
|
||
(Paramétrage > Ma fiscalité).
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{/* ── Vue Report 2042 ── */}
|
||
{view === 'report2042' && (
|
||
<>
|
||
<Report2042Block annee={annee} totauxMois={totauxMois} rates={rates} plateformes={plateformes} excluded={excluded} source="2047" />
|
||
|
||
<div className="card" style={{ marginTop: 12 }}>
|
||
<div style={{ display: 'flex', alignItems: 'baseline', gap: 10, marginBottom: 14 }}>
|
||
<h4 style={{ margin: 0, fontWeight: 700 }}>Crédit d'impôt étranger (cadre 7 du 2047)</h4>
|
||
<span style={{ fontSize: 'var(--fs-xs)', color: 'var(--text-muted)' }}>À reporter sur la 2042C</span>
|
||
</div>
|
||
<div style={{ border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden' }}>
|
||
<Case2042Row
|
||
code="8VL"
|
||
label="Crédit d'impôt sur revenus de valeurs mobilières et assimilés"
|
||
note="Somme des crédits d'impôt retenus (case 237) — plateformes dont le pays ouvre droit à un crédit d'impôt conventionnel"
|
||
value={t.case_8VL}
|
||
/>
|
||
<Case2042Row
|
||
code="8PL"
|
||
label="Revenus nets ouvrant droit à un crédit d'impôt étranger"
|
||
note="Somme des montants nets encaissés (case 233) sur ces mêmes plateformes"
|
||
value={t.case_8PL}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</>
|
||
)}
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
/* ── Ligne de case CERFA 2047 → report 2042/2042C ── */
|
||
function Case2042Row({ code, label, note, value }) {
|
||
return (
|
||
<div style={{ borderBottom: '1px solid var(--border)' }}>
|
||
<div style={{
|
||
display: 'grid', gridTemplateColumns: '90px 1fr auto',
|
||
gap: '0 12px', alignItems: 'center', padding: '10px 14px',
|
||
}}>
|
||
<div style={{
|
||
fontFamily: 'monospace', fontWeight: 800, fontSize: 'var(--fs-base)',
|
||
color: 'var(--primary)', background: 'rgba(99,102,241,0.07)',
|
||
borderRadius: 6, padding: '2px 6px', textAlign: 'center', letterSpacing: '.04em',
|
||
}}>{code}</div>
|
||
<div>
|
||
<div style={{ fontSize: 'var(--fs-sm)', fontWeight: 600 }}>{label}</div>
|
||
{note && <div style={{ fontSize: 'var(--fs-xs)', color: 'var(--text-muted)', marginTop: 2 }}>{note}</div>}
|
||
</div>
|
||
<div style={{ fontWeight: 800, fontSize: 'var(--fs-lg)', whiteSpace: 'nowrap', color: 'var(--text)' }}>
|
||
{Number(value ?? 0).toLocaleString('fr-FR', { minimumFractionDigits: 0, maximumFractionDigits: 0 })} €
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|