From 967be9e750221ab2ab783f95df79bb26d290a45e Mon Sep 17 00:00:00 2001
From: Martial Simon <msimon_fr@hotmail.com>
Date: Mon, 15 Sep 2025 01:07:58 +0200
Subject: add: added projects

---
 .../src/routes/dashboard/analyses/+page.svelte     | 409 +++++++++++++++++++++
 1 file changed, 409 insertions(+)
 create mode 100644 ping/frontend/src/routes/dashboard/analyses/+page.svelte

(limited to 'ping/frontend/src/routes/dashboard/analyses/+page.svelte')

diff --git a/ping/frontend/src/routes/dashboard/analyses/+page.svelte b/ping/frontend/src/routes/dashboard/analyses/+page.svelte
new file mode 100644
index 0000000..1a8ed47
--- /dev/null
+++ b/ping/frontend/src/routes/dashboard/analyses/+page.svelte
@@ -0,0 +1,409 @@
+<script lang="ts">
+	import type { INumberStatList } from '$lib/components/NumberStatList.svelte';
+	import NumberStatList from '$lib/components/NumberStatList.svelte';
+	import SteppedLineChart from '$lib/components/SteppedLineChart.svelte';
+	import { onMount } from 'svelte';
+
+	interface Transaction {
+		id: number;
+		date: Date;
+		amount: number;
+		type: 'buy' | 'sell';
+		recipient: string;
+		company: string;
+		co2Impact: number;
+	}
+
+	const mockTransactions: Transaction[] = [
+		{ id: 1, date: new Date('2025-06-20'), amount: 1200, type: 'buy', recipient: 'Microsoft Corp', company: 'MSFT', co2Impact: 45 },
+		{ id: 2, date: new Date('2025-06-22'), amount: -800, type: 'sell', recipient: 'Apple Inc', company: 'AAPL', co2Impact: -30 },
+		{ id: 3, date: new Date('2025-06-24'), amount: 2500, type: 'buy', recipient: 'Tesla Inc', company: 'TSLA', co2Impact: 15 },
+		{ id: 4, date: new Date('2025-06-26'), amount: 950, type: 'buy', recipient: 'Amazon', company: 'AMZN', co2Impact: 60 },
+		{ id: 5, date: new Date('2025-06-28'), amount: -1500, type: 'sell', recipient: 'Google', company: 'GOOGL', co2Impact: -40 },
+		{ id: 6, date: new Date('2025-06-30'), amount: 750, type: 'buy', recipient: 'Netflix', company: 'NFLX', co2Impact: 25 },
+		{ id: 7, date: new Date('2025-07-01'), amount: 1800, type: 'buy', recipient: 'Microsoft Corp', company: 'MSFT', co2Impact: 50 },
+		{ id: 8, date: new Date('2025-06-15'), amount: -600, type: 'sell', recipient: 'Tesla Inc', company: 'TSLA', co2Impact: -10 },
+		{ id: 9, date: new Date('2025-06-18'), amount: 3200, type: 'buy', recipient: 'NVIDIA', company: 'NVDA', co2Impact: 80 },
+		{ id: 10, date: new Date('2025-06-25'), amount: 450, type: 'buy', recipient: 'Apple Inc', company: 'AAPL', co2Impact: 20 }
+	];
+
+	let transactions = mockTransactions;
+	let selectedPeriod = 30;
+	let analysisData: any = {};
+
+	const thresholds = {
+		dailySpendingLimit: 1000,
+		monthlySpendingLimit: 5000,
+		co2Limit: 100,
+		profitTarget: 2000
+	};
+
+	function calculateStats() {
+		const now = new Date();
+		const cutoffDate = new Date(now.getTime() - selectedPeriod * 24 * 60 * 60 * 1000);
+		const filteredTransactions = transactions.filter(t => t.date >= cutoffDate);
+		
+		const totalAmount = filteredTransactions.reduce((sum, t) => sum + t.amount, 0);
+		const averageAmount = filteredTransactions.length > 0 ? totalAmount / filteredTransactions.length : 0;
+		const uniqueRecipients = new Set(filteredTransactions.map(t => t.recipient)).size;
+		const totalCO2 = filteredTransactions.reduce((sum, t) => sum + t.co2Impact, 0);
+		
+		const recipientTotals = filteredTransactions.reduce((acc, t) => {
+			acc[t.recipient] = (acc[t.recipient] || 0) + t.amount;
+			return acc;
+		}, {} as Record<string, number>);
+		
+		const dailyData = filteredTransactions.reduce((acc, t) => {
+			const date = t.date.toISOString().split('T')[0];
+			acc[date] = (acc[date] || 0) + t.amount;
+			return acc;
+		}, {} as Record<string, number>);
+
+		const chartData = Object.entries(dailyData)
+			.map(([date, amount]) => ({
+				label: new Date(date).toLocaleDateString('fr-FR', { month: 'short', day: 'numeric' }),
+				value: amount
+			}))
+			.sort((a, b) => a.label.localeCompare(b.label));
+
+		const recipientChartData = Object.entries(recipientTotals).map(([recipient, total]) => ({
+			x: recipient,
+			y: Math.abs(total)
+		})).sort((a, b) => b.y - a.y);
+
+		const thresholdAlerts = {
+			dailySpending: Math.abs(totalAmount / selectedPeriod) > thresholds.dailySpendingLimit,
+			monthlySpending: Math.abs(totalAmount * (30 / selectedPeriod)) > thresholds.monthlySpendingLimit,
+			co2Impact: totalCO2 > thresholds.co2Limit,
+			profitTarget: totalAmount >= thresholds.profitTarget
+		};
+
+		analysisData = {
+			totalAmount,
+			averageAmount,
+			uniqueRecipients,
+			totalCO2,
+			chartData,
+			recipientChartData,
+			thresholdAlerts,
+			filteredTransactions
+		};
+	}
+
+	$: statsList = [
+		{
+			name: 'Total Transactions',
+			color: 'aqua',
+			value: `${analysisData.totalAmount ? analysisData.totalAmount.toFixed(0) : '0'}€`,
+			icon: '/icons/wallet.svg'
+		},
+		{
+			name: 'Moyenne',
+			color: '#1FCB4F',
+			value: `${analysisData.averageAmount ? analysisData.averageAmount.toFixed(0) : '0'}€`,
+			icon: '/icons/money-bills.svg'
+		},
+		{
+			name: 'Destinataires',
+			color: 'orange',
+			value: `${analysisData.uniqueRecipients || 0}`,
+			icon: '/icons/people.svg'
+		},
+		{
+			name: 'Impact CO2',
+			color: analysisData.totalCO2 > 0 ? '#ff6b6b' : '#1FCB4F',
+			value: `${analysisData.totalCO2 ? analysisData.totalCO2.toFixed(0) : '0'}g`,
+			icon: '/icons/leaf.svg'
+		}
+	];
+
+	onMount(() => calculateStats());
+	$: selectedPeriod && calculateStats();
+</script>
+
+<section id="analyses">
+	<div class="controls">
+		<h1>Analyses des Transactions</h1>
+		<div class="period-selector">
+			<label for="period">Période d'analyse:</label>
+			<select bind:value={selectedPeriod} id="period">
+				<option value={7}>7 derniers jours</option>
+				<option value={30}>30 derniers jours</option>
+				<option value={90}>3 derniers mois</option>
+				<option value={365}>1 an</option>
+			</select>
+		</div>
+	</div>
+
+	<NumberStatList {statsList} />
+	
+	{#if analysisData.thresholdAlerts}
+		<div class="threshold-alerts">
+			{#if analysisData.thresholdAlerts.dailySpending}
+				<div class="alert alert-warning">
+					⚠️ Dépenses quotidiennes élevées ({(Math.abs(analysisData.totalAmount) / selectedPeriod).toFixed(0)}€/jour)
+				</div>
+			{/if}
+			{#if analysisData.thresholdAlerts.co2Impact}
+				<div class="alert alert-danger">
+					🌍 Impact CO2 élevé ({analysisData.totalCO2}g)
+				</div>
+			{/if}
+			{#if analysisData.thresholdAlerts.profitTarget}
+				<div class="alert alert-success">
+					🎯 Objectif de profit atteint ({analysisData.totalAmount.toFixed(0)}€)
+				</div>
+			{/if}
+		</div>
+	{/if}
+
+	<div id="analysis-grid">
+		<div class="card">
+			<h3>Transactions dans le temps</h3>
+			{#if analysisData.chartData?.length > 0}
+				<SteppedLineChart color="#1FCB4F" data={analysisData.chartData} title="Évolution" legend="Montant (€)" />
+			{:else}
+				<p style="color: #888; text-align: center; padding: 20px;">Aucune donnée</p>
+			{/if}
+		</div>
+		
+		<div class="card">
+			<h3>Top destinataires</h3>
+			<div class="recipients-chart">
+				{#each (analysisData.recipientChartData || []).slice(0, 5) as recipient}
+					<div class="recipient-bar">
+						<span class="recipient-name">{recipient.x}</span>
+						<div class="bar-container">
+							{#if analysisData.recipientChartData?.length > 0}
+								{@const maxValue = Math.max(...analysisData.recipientChartData.map(r => r.y))}
+								<div class="bar" style="width: {(recipient.y / maxValue) * 100}%"></div>
+							{:else}
+								<div class="bar" style="width: 0%"></div>
+							{/if}
+							<span class="recipient-amount">{recipient.y.toFixed(0)}€</span>
+						</div>
+					</div>
+				{/each}
+			</div>
+		</div>
+		
+		<div class="card">
+			<h3>Répartition des transactions</h3>
+			<div class="transaction-breakdown">
+				<div class="breakdown-item">
+					<span class="breakdown-label">Achats:</span>
+					<span class="breakdown-value positive">
+						{(analysisData.filteredTransactions || []).filter((t: Transaction) => t.type === 'buy').length}
+					</span>
+				</div>
+				<div class="breakdown-item">
+					<span class="breakdown-label">Ventes:</span>
+					<span class="breakdown-value negative">
+						{(analysisData.filteredTransactions || []).filter((t: Transaction) => t.type === 'sell').length}
+					</span>
+				</div>
+				<div class="breakdown-item">
+					<span class="breakdown-label">Volume total:</span>
+					<span class="breakdown-value">
+						{Math.abs(analysisData.totalAmount || 0).toFixed(0)}€
+					</span>
+				</div>
+			</div>
+		</div>
+		
+		<div class="card">
+			<h3>Métriques CO2</h3>
+			<div class="co2-metrics">
+				<div class="co2-item">
+					<span class="co2-label">Impact total:</span>
+					<span class="co2-value {analysisData.totalCO2 > 0 ? 'negative' : 'positive'}">
+						{analysisData.totalCO2 || 0}g CO2
+					</span>
+				</div>
+				<div class="co2-item">
+					<span class="co2-label">Par transaction:</span>
+					<span class="co2-value">
+						{(analysisData.filteredTransactions?.length ? analysisData.totalCO2 / analysisData.filteredTransactions.length : 0).toFixed(1)}g
+					</span>
+				</div>
+			</div>
+		</div>
+	</div>
+</section>
+
+<style>
+	.card {
+		margin: 0;
+		background-color: var(--bg-primary);
+		border-radius: 8px;
+		padding: 16px;
+		color: white;
+	}
+
+	.card h3 {
+		margin: 0 0 16px 0;
+		color: var(--text-lime);
+		font-size: 18px;
+		font-weight: 600;
+	}
+
+	#analyses {
+		display: flex;
+		flex-direction: column;
+		padding-right: 12px;
+		background-color: var(--bg-secondary);
+		min-height: calc(100vh - 72px);
+		color: white;
+	}
+
+	.controls {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		padding: 16px 12px;
+		margin-bottom: 8px;
+	}
+
+	.controls h1 {
+		color: var(--text-lime);
+		font-size: 32px;
+		margin: 0;
+		font-weight: 600;
+	}
+
+	.period-selector {
+		display: flex;
+		align-items: center;
+		gap: 8px;
+	}
+
+	.period-selector label {
+		font-size: 14px;
+		color: white;
+	}
+
+	.period-selector select {
+		background-color: var(--bg-primary);
+		color: white;
+		border: 1px solid #444;
+		border-radius: 4px;
+		padding: 8px 12px;
+		font-size: 14px;
+	}
+
+	.threshold-alerts {
+		padding: 0 12px;
+		margin-bottom: 16px;
+	}
+
+	.alert {
+		padding: 12px 16px;
+		border-radius: 8px;
+		margin-bottom: 8px;
+		font-size: 14px;
+		font-weight: 500;
+	}
+
+	.alert-warning {
+		background-color: rgba(255, 193, 7, 0.2);
+		border-left: 4px solid #ffc107;
+		color: #ffc107;
+	}
+
+	.alert-danger {
+		background-color: rgba(220, 53, 69, 0.2);
+		border-left: 4px solid #dc3545;
+		color: #dc3545;
+	}
+
+	.alert-success {
+		background-color: rgba(40, 167, 69, 0.2);
+		border-left: 4px solid #28a745;
+		color: #28a745;
+	}
+
+	#analysis-grid {
+		padding: 0 12px;
+		width: 100%;
+		display: grid;
+		grid-template-columns: repeat(2, 1fr);
+		gap: 1rem;
+	}
+
+	.recipients-chart {
+		display: flex;
+		flex-direction: column;
+		gap: 12px;
+	}
+
+	.recipient-bar {
+		display: flex;
+		flex-direction: column;
+		gap: 4px;
+	}
+
+	.recipient-name {
+		font-size: 12px;
+		color: #888;
+		font-weight: 500;
+	}
+
+	.bar-container {
+		display: flex;
+		align-items: center;
+		gap: 8px;
+		position: relative;
+	}
+
+	.bar {
+		height: 20px;
+		background: linear-gradient(90deg, var(--text-lime), #1FCB4F);
+		border-radius: 4px;
+		transition: width 0.3s ease;
+	}
+
+	.recipient-amount {
+		font-size: 12px;
+		color: white;
+		font-weight: 600;
+		min-width: 60px;
+		text-align: right;
+	}
+
+	.transaction-breakdown, .co2-metrics {
+		display: flex;
+		flex-direction: column;
+		gap: 12px;
+	}
+
+	.breakdown-item, .co2-item {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		padding: 8px 0;
+		border-bottom: 1px solid #333;
+	}
+
+	.breakdown-item:last-child, .co2-item:last-child {
+		border-bottom: none;
+	}
+
+	.breakdown-label, .co2-label {
+		font-size: 14px;
+		color: #888;
+	}
+
+	.breakdown-value, .co2-value {
+		font-size: 16px;
+		font-weight: 600;
+		color: white;
+	}
+
+	.breakdown-value.positive, .co2-value.positive {
+		color: var(--text-lime);
+	}
+
+	.breakdown-value.negative, .co2-value.negative {
+		color: #ff6b6b;
+	}
+</style>
-- 
cgit v1.2.3

