Ver código fonte

update insights

Daniel Bohry 9 meses atrás
pai
commit
5f81cc9e42

+ 0 - 77
src/components/Chart.svelte

@@ -1,77 +0,0 @@
-<script>
-	import { onMount } from 'svelte';
-	import { ArcElement, Chart, DoughnutController, Legend, Title, Tooltip } from 'chart.js';
-
-	export let data = {};
-
-	let chartContainer;
-
-	onMount(() => {
-		Chart.register(ArcElement, Tooltip, Legend, Title, DoughnutController);
-
-		if (data && data.stocks) {
-			const stockNames = data.stocks.map((stock) => stock.name);
-			const stockTotals = data.stocks.map((stock) => stock.total);
-
-			const chartData = {
-				labels: stockNames,
-				datasets: [
-					{
-						label: 'Stock Distribution',
-						data: stockTotals,
-						backgroundColor: [
-							'#FF6F61',
-							'#6B8E23',
-							'#FFD700',
-							'#20B2AA',
-							'#87CEFA',
-							'#D2691E',
-							'#FF6347',
-							'#98FB98',
-							'#D8BFD8',
-							'#20B2AA',
-							'#FF4500',
-							'#8A2BE2',
-							'#A52A2A',
-							'#DEB887',
-							'#5F9EA0'
-						],
-						borderColor: '#fff',
-						borderWidth: 1
-					}
-				]
-			};
-
-			const chartOptions = {
-				responsive: true,
-				plugins: {
-					legend: {
-						position: 'top'
-					},
-					tooltip: {
-						callbacks: {
-							label: function(tooltipItem) {
-								return `${tooltipItem.label}: $${tooltipItem.raw.toFixed(2)}`;
-							}
-						}
-					}
-				}
-			};
-
-			new Chart(chartContainer, {
-				type: 'doughnut',
-				data: chartData,
-				options: chartOptions
-			});
-		}
-	});
-</script>
-
-<div>
-	{#if data && data.stocks && data.stocks.length > 0}
-		<canvas bind:this={chartContainer}></canvas>
-	{:else}
-		<p>No insights available.</p>
-	{/if}
-</div>
-

+ 147 - 0
src/components/CurrentPositionChart.svelte

@@ -0,0 +1,147 @@
+<script>
+	import { ArcElement, Chart, DoughnutController, Legend, Title, Tooltip } from 'chart.js';
+	import { onDestroy } from 'svelte';
+
+	export let data = {};
+	export let currency = 'USD';
+	export let total = 0;
+
+	let chartContainer;
+	let chartInstance;
+
+	function formatCurrency(value, fmtCurrency) {
+		return value.toLocaleString('en-US', {
+			style: 'currency',
+			currency: fmtCurrency
+		});
+	}
+
+	const centerTextPlugin = {
+		id: 'centerText',
+		beforeDraw(chart) {
+			const centerTextOptions = chart.options.plugins.centerText;
+			if (!centerTextOptions || centerTextOptions.displayTotal === undefined || !centerTextOptions.displayCurrency) {
+				return;
+			}
+
+			const displayTotal = centerTextOptions.displayTotal;
+			const displayCurrency = centerTextOptions.displayCurrency;
+
+			const { width, height, ctx } = chart;
+			ctx.save();
+
+			ctx.font = 'bold 20px sans-serif';
+			ctx.fillStyle = '#333';
+			ctx.textAlign = 'center';
+			ctx.textBaseline = 'middle';
+
+			const text = `${formatCurrency(displayTotal, displayCurrency)}`;
+			ctx.fillText(text, width / 2, height / 2);
+
+			ctx.restore();
+		}
+	};
+
+	Chart.register(ArcElement, Tooltip, Legend, Title, DoughnutController, centerTextPlugin);
+
+	$: if (chartContainer && data?.stocks) {
+		updateChart();
+	}
+
+	function updateChart() {
+		if (chartInstance) {
+			chartInstance.destroy();
+		}
+
+		if (!data || !data.stocks || data.stocks.length === 0) {
+			chartInstance = null;
+			return;
+		}
+
+		const stockNames = data.stocks.map((stock) => stock.name);
+		const stockTotals = data.stocks.map((stock) => stock.total);
+
+		const chartData = {
+			labels: stockNames,
+			datasets: [
+				{
+					label: 'Stock Distribution',
+					data: stockTotals,
+					backgroundColor: [
+						'#1abc9c', '#2ecc71', '#3498db', '#9b59b6', '#f1c40f',
+						'#e67e22', '#e74c3c', '#34495e', '#16a085', '#27ae60',
+						'#2980b9', '#8e44ad', '#f39c12', '#d35400', '#c0392b'
+					],
+					borderColor: '#fff',
+					borderWidth: 1
+				}
+			]
+		};
+
+		const chartOptions = {
+			responsive: true,
+			maintainAspectRatio: false,
+			plugins: {
+				legend: {
+					display: false
+				},
+				tooltip: {
+					callbacks: {
+						label: function(tooltipItem) {
+							return `${tooltipItem.label}: ${formatCurrency(tooltipItem.raw, currency)}`;
+						}
+					}
+				},
+				centerText: {
+					displayTotal: total,
+					displayCurrency: currency
+				}
+			}
+		};
+
+		chartInstance = new Chart(chartContainer, {
+			type: 'doughnut',
+			data: chartData,
+			options: chartOptions
+		});
+	}
+
+	onDestroy(() => {
+		if (chartInstance) {
+			chartInstance.destroy();
+		}
+	});
+
+</script>
+
+<div class="chart-wrapper">
+	{#if data && data.stocks && data.stocks.length > 0}
+		<p class="chart-title">Current Portfolio Positions</p>
+		<canvas bind:this={chartContainer}></canvas>
+	{:else}
+		<p>No insights available.</p>
+	{/if}
+</div>
+
+<style>
+    .chart-wrapper {
+        width: 400px;
+        height: 400px;
+        max-width: 100%;
+        margin: auto;
+        position: relative;
+    }
+
+    canvas {
+        width: 100% !important;
+        height: 100% !important;
+    }
+
+    .chart-title {
+        text-align: center;
+        font-weight: bold;
+        margin-bottom: 0.5rem;
+        font-size: 1rem;
+        color: #333;
+    }
+</style>

+ 33 - 7
src/routes/insights/+page.svelte

@@ -1,5 +1,5 @@
 <script>
-	import Chart from '../../components/Chart.svelte';
+	import CurrentPositionChart from '../../components/CurrentPositionChart.svelte';
 	import { onMount } from 'svelte';
 	import { authentication } from '../store.js';
 	import { fade } from 'svelte/transition';
@@ -9,6 +9,9 @@
 	let authToken;
 	let data = {};
 	let isLoading = true;
+	let currency = 'USD';
+	let total = 0;
+	// Removed chartKey
 
 	onMount(() => {
 		return authentication.subscribe(async (auth) => {
@@ -23,18 +26,29 @@
 
 	async function fetchPortfolio() {
 		try {
-			const response = await getRequest(`${import.meta.env.VITE_STOCKS_HOST}/api/portfolios`, {}, authToken);
+			const response = await getRequest(
+				`${import.meta.env.VITE_STOCKS_HOST}/api/portfolios?currency=${currency}`,
+				{},
+				authToken
+			);
 
 			if (response.ok) {
 				const portfolio = await response.json();
-
 				if (portfolio?.length) {
-					data = portfolio[0];
-					data.stocks = data.stocks.sort((a, b) => a.total - b.total);
+					data = {
+						...portfolio[0],
+						stocks: [...portfolio[0].stocks].sort((a, b) => a.total - b.total)
+					};
+					total = portfolio[0].totalValue;
+				} else {
+					data = {};
+					total = 0;
 				}
 			} else {
 				const error = await response.json();
 				console.error('Failed to find portfolio info:', error);
+				data = {};
+				total = 0;
 			}
 		} catch (error) {
 			console.error('Failed to fetch insights:', error);
@@ -43,6 +57,12 @@
 			isLoading = false;
 		}
 	}
+
+	function updateCurrency(event) {
+		currency = event.target.value;
+		isLoading = true;
+		fetchPortfolio();
+	}
 </script>
 
 <svelte:head>
@@ -54,6 +74,12 @@
 	<div in:fade>Loading...</div>
 {:else}
 	<div in:fade>
-		<Chart {data} />
+		<select class="form-control order-select" on:change={updateCurrency} value={currency}>
+			<option value="BRL">BRL</option>
+			<option value="EUR">EUR</option>
+			<option value="USD" selected>USD</option>
+		</select>
+
+		<CurrentPositionChart {data} {currency} {total} />
 	</div>
-{/if}
+{/if}