import { useState, useRef, useEffect } from 'react';
import { api } from '../api.js';
import { useInteretsChart } from '../context/InteretsChartContext.jsx';
const ICONS_BASE = '/api/icons-files/';
const GRID = 'rgba(255,255,255,0.055)';
const LABEL = '#4a5568';
const MOIS = ['Jan','Fév','Mar','Avr','Mai','Jun','Jul','Aoû','Sep','Oct','Nov','Déc'];
function fmtShort(v) {
if (!v || v === 0) return '';
const abs = Math.abs(v);
if (abs >= 1000) return (v/1000).toLocaleString('fr-FR',{maximumFractionDigits:1})+'k €';
return v.toLocaleString('fr-FR',{minimumFractionDigits:1,maximumFractionDigits:1})+' €';
}
function fmtTotal(v) {
return v.toLocaleString('fr-FR',{minimumFractionDigits:2,maximumFractionDigits:2})+' €';
}
function hexToRgba(hex, a) {
if (!hex||hex.length<7) return `rgba(79,168,232,${a})`;
const r=parseInt(hex.slice(1,3),16), g=parseInt(hex.slice(3,5),16), b=parseInt(hex.slice(5,7),16);
return `rgba(${r},${g},${b},${a})`;
}
export default function InteretsMensuelsChart() {
const {
annee, setAnnee, availableYears,
inclureInterets, setInclureInterets,
inclureCapital, setInclureCapital,
inclureCashback, setInclureCashback,
selectedMonth, setSelectedMonth,
showActual, toggleActual,
showProjected, toggleProjected,
months, annualTotal,
// Mode global (TOUT)
modeGlobal, toggleModeGlobal,
selectedYear, setSelectedYear,
years, globalTotal,
netMode,
chartInterets, chartCapital, chartCashback,
} = useInteretsChart();
// État local uniquement
const [hovered, setHovered] = useState(null);
const [windowStart, setWindowStart] = useState(0);
const [libIcons, setLibIcons] = useState({});
const [isMobile, setIsMobile] = useState(() => typeof window !== 'undefined' && window.innerWidth < 768);
const [sheetOpen, setSheetOpen] = useState(false);
const initializedRef = useRef(false);
useEffect(() => {
api.get('/icons').then(rows => {
const m = {};
rows.forEach(r => { m[r.name] = r.filename; });
setLibIcons(m);
}).catch(() => {});
}, []);
useEffect(() => {
const onResize = () => setIsMobile(window.innerWidth < 768);
window.addEventListener('resize', onResize);
return () => window.removeEventListener('resize', onResize);
}, []);
// Réinitialiser le hovered au changement de mode
useEffect(() => { setHovered(null); }, [modeGlobal]);
const visibleYears = availableYears.length ? availableYears.slice(windowStart, windowStart+3) : [annee];
const canPrev = windowStart > 0;
const canNext = windowStart + 3 < availableYears.length;
useEffect(() => {
if (!availableYears.length || initializedRef.current) return;
initializedRef.current = true;
const idx = availableYears.indexOf(annee);
const safe = idx >= 0 ? idx : availableYears.length - 1;
setWindowStart(Math.max(0, Math.min(availableYears.length - 3, safe - 1)));
}, [availableYears]);
/* ── Données affichées selon le mode ── */
const items = modeGlobal ? years : months;
const barCount = items.length;
/* ── SVG layout ── */
const W = 900, H = 280;
const PAD = { top: 52, right: 20, bottom: 34, left: 70 };
const plotW = W - PAD.left - PAD.right;
const plotH = H - PAD.top - PAD.bottom;
const gap = modeGlobal ? 10 : 8;
const barW = barCount > 0 ? Math.floor((plotW - gap * (barCount - 1)) / barCount) : 60;
const barBotY = PAD.top + plotH;
const filteredTotal = item =>
(showActual ? item.capitalAmt + item.cashbackAmt + item.interetsAmt : 0) +
(showProjected ? item.capitalProjAmt + item.interetsProjAmt : 0);
const filteredSum = items.reduce((s, item) => s + filteredTotal(item), 0);
const rawMax = Math.max(...items.map(item => filteredTotal(item)), 0.01);
const niceStep = (() => {
const raw = rawMax/4, mag = Math.pow(10,Math.floor(Math.log10(raw))), n = raw/mag;
return (n<1.5?1:n<3.5?2:n<7.5?5:10)*mag;
})();
const niceMax = Math.ceil(rawMax/niceStep)*niceStep || niceStep;
const yScale = v => PAD.top + plotH - (v/niceMax)*plotH;
const barX = i => PAD.left + i*(barW+gap);
const yTicks = Array.from({length: Math.round(niceMax/niceStep)+1}, (_,i) => ({v:i*niceStep, y:yScale(i*niceStep)}));
/* ── Tooltip ── */
const tooltipItem = hovered !== null ? items[hovered] : null;
const tooltipBCX = hovered !== null ? barX(hovered) + barW/2 : 0;
const tooltipBTY = hovered !== null ? yScale(filteredTotal(items[hovered])) : 0;
const anchorRight = tooltipBCX / W > 0.65;
const buildSegments = (item) => {
const actual = [];
if (showActual) {
if (inclureCapital && item.capitalAmt > 0) actual.push({color:chartCapital, v:item.capitalAmt});
if (inclureCashback && item.cashbackAmt > 0) actual.push({color:chartCashback, v:item.cashbackAmt});
if (inclureInterets && item.interetsAmt > 0) actual.push({color:chartInterets, v:item.interetsAmt});
}
const projected = [];
if (showProjected) {
if (inclureCapital && item.capitalProjAmt > 0) projected.push({color:chartCapital, v:item.capitalProjAmt});
if (inclureInterets && item.interetsProjAmt > 0) projected.push({color:chartInterets, v:item.interetsProjAmt});
}
return { actual, projected };
};
const activeTypes = [
inclureCapital && {key:'capital', color:chartCapital, label:'Capital'},
inclureCashback && {key:'cashback', color:chartCashback, label:'Cashback'},
inclureInterets && {key:'interets', color:chartInterets, label:netMode?'Intérêts nets':'Intérêts bruts'},
].filter(Boolean);
const activeTypeCount = [inclureInterets, inclureCapital, inclureCashback].filter(Boolean).length;
const AppIcon = ({ name, size = 28, active = false }) => {
const filename = libIcons[name];
if (filename) return (
);
return ;
};
/* ── Label en-tête ── */
return (
{/* ── En-tête ── */}
{inclureInterets && (
{netMode?'Intérêts nets':'Intérêts bruts'}
)}
{inclureCapital && (
Capital
)}
{inclureCashback && (
Cashback
)}
{!inclureInterets && !inclureCapital && !inclureCashback && (
—
)}
· {modeGlobal?'Toutes les années':annee}
{fmtTotal(filteredSum)}
{isMobile ? (
/* ── Mobile : bouton Filtres ── */
) : (
/* ── Desktop : controls existants ── */
{!modeGlobal && <>
{visibleYears.map(y=>(
))}
>}
)}
{/* ── SVG bar chart ── */}
{/* Tooltip */}
{tooltipItem&&tooltipItem.total>0&&(
{modeGlobal
? `${tooltipItem.label}${tooltipItem.isCurrent?' · année en cours':''}`
: `${MOIS[tooltipItem.m-1]} ${annee}${tooltipItem.isCurrentMonth?' · mois en cours':''}`
}
{(()=>{
const ROW=({label,value,color=null,indent=false,muted=false})=>(
{color&&}
{label}
{value}
);
const hasMix=tooltipItem.actual>0&&tooltipItem.projected>0;
const multi=[inclureCapital,inclureCashback,inclureInterets].filter(Boolean).length>1;
return(<>
{tooltipItem.actual>0&&<>
{multi&&inclureCapital &&tooltipItem.capitalAmt >0&&
}
{multi&&inclureCashback&&tooltipItem.cashbackAmt>0&&
}
{multi&&inclureInterets&&tooltipItem.interetsAmt>0&&
}
{!multi&&inclureInterets&&
}
{!multi&&inclureCapital &&
}
{!multi&&inclureCashback&&
}
>}
{tooltipItem.projected>0&&<>
{multi&&inclureCapital &&tooltipItem.capitalProjAmt >0&&
}
{multi&&inclureInterets&&tooltipItem.interetsProjAmt>0&&
}
{!multi&&inclureInterets&&
}
{!multi&&inclureCapital&&tooltipItem.capitalProjAmt>0&&
}
>}
{hasMix&&Total{fmtTotal(tooltipItem.total)}}
{!hasMix&&Total{fmtTotal(tooltipItem.total)}}
>);
})()}
)}
{/* ── Légende + sélecteur Reçu/Projeté ── */}
{/* Sélecteur Reçu/Projeté — desktop uniquement (mobile → bottom sheet) */}
{!isMobile && (
{[
{key:'actual', label:'Reçu', active:showActual, toggle:toggleActual},
{key:'projected', label:'Projeté', active:showProjected, toggle:toggleProjected},
].map(btn=>(
))}
)}
{/* Légende couleurs */}
{activeTypes.map(t=>(
{showActual&&(
{t.label}{t.key==='interets'?' reçus':' reçu'}
)}
{showProjected&&(
{t.label}{t.key==='interets'?' projetés':' projeté'}
)}
))}
{/* ── Bottom sheet mobile ── */}
{isMobile && (
<>
{/* Overlay */}
setSheetOpen(false)}
style={{
position:'fixed', inset:0, zIndex:200,
background:'rgba(0,0,0,0.45)',
opacity: sheetOpen ? 1 : 0,
pointerEvents: sheetOpen ? 'auto' : 'none',
transition:'opacity .25s',
}}
/>
{/* Sheet */}
{/* Handle */}
{/* Types */}
Types
{[
{key:'interets', label:netMode?'Intérêts nets':'Intérêts bruts', color:chartInterets, active:inclureInterets, set:setInclureInterets},
{key:'capital', label:'Capital', color:chartCapital, active:inclureCapital, set:setInclureCapital},
{key:'cashback', label:'Cashback', color:chartCashback, active:inclureCashback, set:setInclureCashback},
].map((t,i,arr)=>(
t.set(v=>!v)} style={{
display:'flex', alignItems:'center', justifyContent:'space-between',
padding:'12px 0',
borderBottom: i
{t.label}
{/* Toggle */}
))}
{/* Période */}
Période
{availableYears.map(y=>(
))}
{/* Affichage */}
Affichage
{[
{key:'actual', label:'Reçu', active:showActual, toggle:toggleActual},
{key:'projected', label:'Projeté', active:showProjected, toggle:toggleProjected},
].map((btn,i,arr)=>(
btn.toggle()} style={{
display:'flex', alignItems:'center', justifyContent:'space-between',
padding:'12px 0',
borderBottom: i
{btn.label}
))}
>
)}
);
}