import React, { useState, useEffect, useMemo } from 'react';
import {
LineChart,
Wallet,
ArrowUpRight,
ArrowDownRight,
Activity,
DollarSign,
PieChart,
RefreshCw,
AlertCircle,
TrendingUp,
CreditCard,
Calendar
} from 'lucide-react';
// --- Composants UI ---
const Card = ({ children, className = '' }) => (
{children}
);
const Badge = ({ children, type = 'neutral' }) => {
const colors = {
success: 'bg-emerald-100 text-emerald-700',
danger: 'bg-rose-100 text-rose-700',
neutral: 'bg-slate-100 text-slate-700',
warning: 'bg-amber-100 text-amber-800'
};
return (
{children}
);
};
// --- Données Simulées (Fallback) ---
const MOCK_DATA = {
portfolio: {
totalValue: 124592.45,
dayChange: 1245.30,
dayChangePercent: 1.01,
cashBalance: 15400.00
},
assets: [
{ id: 1, symbol: 'AAPL', name: 'Apple Inc.', quantity: 150, price: 178.35, change: 1.25, type: 'stock' },
{ id: 2, symbol: 'MSFT', name: 'Microsoft Corp.', quantity: 80, price: 332.40, change: -0.45, type: 'stock' },
{ id: 3, symbol: 'VTI', name: 'Vanguard Total Stock', quantity: 200, price: 225.10, change: 0.85, type: 'etf' },
{ id: 4, symbol: 'BTC', name: 'Bitcoin', quantity: 0.45, price: 42150.00, change: 2.30, type: 'crypto' },
{ id: 5, symbol: 'TSLA', name: 'Tesla Inc.', quantity: 45, price: 245.60, change: -1.20, type: 'stock' },
],
history: Array.from({ length: 14 }).map((_, i) => ({
date: new Date(Date.now() - (13 - i) * 24 * 60 * 60 * 1000).toLocaleDateString('fr-FR', { day: '2-digit', month: 'short' }),
value: 120000 + Math.random() * 5000 + (i * 300)
})),
transactions: [
{ id: 101, type: 'buy', asset: 'VTI', amount: 2250.00, date: '2023-10-25', status: 'completed' },
{ id: 102, type: 'dividend', asset: 'MSFT', amount: 45.20, date: '2023-10-24', status: 'completed' },
{ id: 103, type: 'deposit', asset: 'CAD', amount: 1000.00, date: '2023-10-22', status: 'completed' },
{ id: 104, type: 'sell', asset: 'TSLA', amount: 1245.50, date: '2023-10-20', status: 'completed' },
]
};
// --- Composant Principal ---
export default function FinanceDashboard() {
const [loading, setLoading] = useState(true);
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [activeTab, setActiveTab] = useState('overview');
const [usingMock, setUsingMock] = useState(false);
// Fonction pour charger les données (simule un appel API)
const fetchData = async () => {
setLoading(true);
setError(null);
try {
// Tentative de connexion à l'API (Simulée ici pour échouer et passer au fallback)
// Dans un cas réel, ce serait: const response = await fetch('https://finance.jeva.ca/api.php');
// Simulation d'un échec réseau pour démontrer le fallback
const shouldFail = true;
if (shouldFail) {
throw new Error("API non trouvée (500)");
}
// Si succès (code inatteignable ici)
// const result = await response.json();
// setData(result);
} catch (err) {
console.warn("Mode API échoué, passage en mode Démo:", err.message);
setUsingMock(true);
// Délai artificiel pour l'UX
await new Promise(resolve => setTimeout(resolve, 800));
setData(MOCK_DATA);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, []);
// Calculs dérivés
const assetAllocation = useMemo(() => {
if (!data) return [];
const types = data.assets.reduce((acc, asset) => {
const value = asset.quantity * asset.price;
acc[asset.type] = (acc[asset.type] || 0) + value;
return acc;
}, {});
const total = Object.values(types).reduce((a, b) => a + b, 0);
return Object.entries(types).map(([name, value]) => ({
name,
value,
percent: (value / total) * 100
}));
}, [data]);
// Rendu du graphique simple en SVG
const SimpleChart = ({ data, color = "#10b981" }) => {
if (!data || data.length === 0) return null;
const height = 160; // h-40
const width = 100; // pourcentage
const min = Math.min(...data.map(d => d.value));
const max = Math.max(...data.map(d => d.value));
const range = max - min;
const points = data.map((d, i) => {
const x = (i / (data.length - 1)) * 100;
const y = 100 - ((d.value - min) / range) * 100;
return `${x},${y}`;
}).join(' ');
return (
{/* Tooltip simulé au survol pourrait être ajouté ici */}
);
};
if (loading) {
return (
Chargement du portefeuille...
);
}
return (
{/* Top Navigation */}
{/* Main Content */}
{/* Header Stats */}
Tableau de Bord
+{data.portfolio.dayChangePercent}%
Valeur Totale
{data.portfolio.totalValue.toLocaleString('fr-FR', { style: 'currency', currency: 'CAD' })}
+{data.portfolio.dayChange.toLocaleString('fr-FR', { style: 'currency', currency: 'CAD' })} aujourd'hui
{assetAllocation.map((item) => (
{item.name === 'stock' ? 'Actions' : item.name === 'etf' ? 'FNBs' : 'Crypto'}
{item.value.toLocaleString('fr-FR', { style: 'currency', currency: 'CAD', maximumFractionDigits: 0 })}
{item.percent.toFixed(0)}%
))}
{/* Main Sections Tab */}
{/* Content based on Tab */}
{activeTab === 'overview' && (
Top Mouvements
{data.assets.slice(0, 4).map(asset => (
{asset.symbol[0]}
{asset.symbol}
{asset.name}
{(asset.price).toLocaleString('fr-FR', { style: 'currency', currency: 'USD' })}
= 0 ? 'text-emerald-600' : 'text-rose-600'}`}>
{asset.change >= 0 ? : }
{asset.change > 0 ? '+' : ''}{asset.change}%
))}
Activité Récente
{data.transactions.map(tx => (
{tx.type === 'deposit' ?
:
tx.type === 'sell' ?
:
tx.type === 'buy' ?
:
}
{tx.type} {tx.asset}
{new Date(tx.date).toLocaleDateString()}
{tx.type === 'deposit' || tx.type === 'sell' || tx.type === 'dividend' ? '+' : '-'}
{tx.amount.toLocaleString('fr-FR', { style: 'currency', currency: 'CAD' })}
))}
)}
{activeTab === 'holdings' && (
| Actif |
Prix |
Quantité |
Valeur Totale |
Variation (24h) |
{data.assets.map((asset) => (
{asset.symbol[0]}
{asset.symbol}
{asset.name}
|
{asset.price.toLocaleString('fr-FR', { style: 'currency', currency: 'USD' })}
|
{asset.quantity}
|
{(asset.price * asset.quantity).toLocaleString('fr-FR', { style: 'currency', currency: 'CAD' })}
|
= 0 ? 'success' : 'danger'}>
{asset.change > 0 ? '+' : ''}{asset.change}%
|
))}
)}
);
}