Implémentation de la feature "suivi des objectifs"

This commit is contained in:
ocroguennec committed 2026-08-17 17:15:50 +02:00
1 parent 6e03195d3a
commit 1060bfe2be
14 files changed
+1844 -192

No files matched your search

+21 -8
View File
@@ -6,6 +6,7 @@ import EmptyState from '../components/EmptyState.jsx';
import { useLocation, useNavigate } from 'react-router-dom';
import { api } from '../api.js';
import { useInvestisseur } from '../context/InvestisseurContext.jsx';
import { useUi } from '../context/UiContext.jsx';
import Modal from '../components/Modal.jsx';
import ConfirmModal from '../components/ConfirmModal.jsx';
import SoldeChart from '../components/SoldeChart.jsx';
@@ -94,6 +95,7 @@ function platToJSON(rows) {
/* ── Composant principal ─────────────────────────────────────── */
export default function DepotsRetraits() {
const { activeId, activeView, investisseurs } = useInvestisseur();
const { objectifsActifs } = useUi();
const { search } = useLocation();
const navigate = useNavigate();
const [activeTab, setActiveTab] = useState('plateformes');
@@ -453,6 +455,12 @@ export default function DepotsRetraits() {
totalPages: drTotalPages, totalItems: drTotalItems, PAGE_SIZES,
} = usePagination(tableRows, 'cl_pagesize_dr', [filter]);
// Repli sur l'onglet Plateformes si la fixation des objectifs est désactivée
// pendant qu'on est sur l'onglet Objectifs (ex. dans un autre onglet du navigateur).
useEffect(() => {
if (!objectifsActifs && activeTab === 'vision-annuelle') setActiveTab('plateformes');
}, [objectifsActifs, activeTab]);
// Auto-sélectionne le premier mouvement quand la liste change dans l'onglet mouvements
// (déclaré après `tableRows` pour éviter la temporal dead zone)
useEffect(() => {
@@ -575,6 +583,7 @@ export default function DepotsRetraits() {
Toujours calculé sur le portefeuille entier (ignore le filtre plateforme),
pour l'année sélectionnée (drPlatYear, ou l'année en cours si "Toutes les années"). */
const scopeInvestisseurIdsKpi = activeView === 'all' ? investisseurs.map(i => i.id) : [activeId];
const kpiObjectifYear = Number(drPlatYear || new Date().getFullYear());
const kpiObjectifTotal = (() => {
const list = objectifsKpi.filter(o => o.annee === kpiObjectifYear && scopeInvestisseurIdsKpi.includes(o.investisseur_id));
@@ -904,14 +913,16 @@ export default function DepotsRetraits() {
<TrendBadge current={totals.depots - totals.retraits} prev={prevTotals.depots - prevTotals.retraits} />
</div>
<div style={{ fontSize: '0.8em', color: 'var(--text-muted)', marginTop: 5 }}>{fmtEUR(prevTotals.depots - prevTotals.retraits)} en {prevYear}</div>
{kpiEcartObjectif != null && (
{objectifsActifs && kpiEcartObjectif != null && (
<div style={{
fontSize: '0.8em', marginTop: 4, fontWeight: 600,
color: kpiEcartObjectif >= 0 ? 'var(--success)' : 'var(--warning)',
color: Math.abs(kpiEcartObjectif) < 0.01 ? 'var(--success)' : kpiEcartObjectif >= 0 ? 'var(--danger)' : 'var(--warning)',
}}>
{kpiEcartObjectif >= 0
? `+${fmtEUR(kpiEcartObjectif)} au-delà de l'objectif ${kpiObjectifYear}`
: `Reste ${fmtEUR(-kpiEcartObjectif)} pour l'objectif ${kpiObjectifYear}`}
{Math.abs(kpiEcartObjectif) < 0.01
? `Conforme à l'objectif ${kpiObjectifYear}`
: kpiEcartObjectif >= 0
? `+${fmtEUR(kpiEcartObjectif)} au-delà de l'objectif ${kpiObjectifYear}`
: `${fmtEUR(-kpiEcartObjectif)} en dessous de l'objectif ${kpiObjectifYear}`}
</div>
)}
</div>
@@ -929,16 +940,18 @@ export default function DepotsRetraits() {
{!listFocused && <div className="dr-tabs">
<button className={`dr-tab${activeTab === 'plateformes' ? ' active' : ''}`}
onClick={() => setActiveTab('plateformes')}>Plateformes</button>
<button className={`dr-tab${activeTab === 'vision-annuelle' ? ' active' : ''}`}
onClick={() => setActiveTab('vision-annuelle')}>Vision annuelle</button>
<button className={`dr-tab${activeTab === 'vision-mensuelle' ? ' active' : ''}`}
onClick={() => setActiveTab('vision-mensuelle')}>Vision mensuelle</button>
<button className={`dr-tab${activeTab === 'mouvements' ? ' active' : ''}`}
onClick={() => { setActiveTab('mouvements'); setSelectedRow(r => r ?? (rows[0] || null)); }}>Mouvements</button>
{objectifsActifs && (
<button className={`dr-tab${activeTab === 'vision-annuelle' ? ' active' : ''}`}
onClick={() => setActiveTab('vision-annuelle')}>Objectifs</button>
)}
</div>}
{/* ====== ONGLET VISION ANNUELLE ====== */}
{activeTab === 'vision-annuelle' && (
{objectifsActifs && activeTab === 'vision-annuelle' && (
<div style={{ padding: '0 24px' }}>
<SuiviObjectifs
rows={allRows}