Przeglądaj źródła

add current portfolio value to history chart

Daniel Bohry 3 tygodni temu
rodzic
commit
1c2f1e044b

+ 133 - 33
src/components/PortfolioValueHistory.svelte

@@ -8,6 +8,7 @@
 	export let portfolioId;
 	export let authToken;
 	export let currency = localStorage.getItem('defaultCurrency') || 'USD';
+	export let liveCurrentValue = null;
 
 	let chartCanvas;
 	let chartInstance;
@@ -16,6 +17,9 @@
 	let currentValue = null;
 	let valueChange = null;
 	let valueChangePercent = null;
+	let lastHistoricalValue = null;
+	let showCurrentPoint = false;
+	let currentValueDifferencePercent = null;
 
 	async function fetchData() {
 		try {
@@ -28,6 +32,7 @@
 
 				const firstValue = historyData[0].totalValue;
 				const lastValue = historyData[historyData.length - 1].totalValue;
+				lastHistoricalValue = lastValue;
 				currentValue = lastValue;
 				valueChange = lastValue - firstValue;
 				valueChangePercent = firstValue !== 0 ? ((lastValue - firstValue) / firstValue) * 100 : 0;
@@ -79,26 +84,70 @@
 		gradientFill.addColorStop(0, 'rgba(59, 130, 246, 0.3)');
 		gradientFill.addColorStop(1, 'rgba(59, 130, 246, 0.05)');
 
+		let chartLabels = [...labels];
+		const datasets = [];
+
+		const historicalDataset = {
+			label: `Portfolio Value (${currency})`,
+			data: values,
+			fill: true,
+			backgroundColor: gradientFill,
+			borderColor: 'rgb(59, 130, 246)',
+			borderWidth: 3,
+			pointBackgroundColor: 'rgb(59, 130, 246)',
+			pointBorderColor: 'white',
+			pointBorderWidth: 2,
+			pointRadius: 5,
+			pointHoverRadius: 7,
+			tension: 0.4
+		};
+
+		datasets.push(historicalDataset);
+
+		if (showCurrentPoint && liveCurrentValue !== null && values.length > 0) {
+			const now = new Date();
+			const currentLabel = now.toLocaleDateString('en-US', { month: 'short', year: 'numeric' });
+			chartLabels = [...labels, currentLabel];
+
+			const connectionData = Array(chartLabels.length).fill(null);
+			connectionData[values.length - 1] = values[values.length - 1];
+			connectionData[values.length] = liveCurrentValue;
+
+			datasets.push({
+				label: 'Current Value',
+				data: connectionData,
+				fill: false,
+				backgroundColor: 'transparent',
+				borderColor: 'rgb(34, 197, 94)',
+				borderWidth: 3,
+				borderDash: [10, 5],
+				pointBackgroundColor: connectionData.map((val, idx) =>
+					idx === values.length ? 'rgb(34, 197, 94)' : 'transparent'
+				),
+				pointBorderColor: connectionData.map((val, idx) =>
+					idx === values.length ? 'white' : 'transparent'
+				),
+				pointBorderWidth: connectionData.map((val, idx) =>
+					idx === values.length ? 3 : 0
+				),
+				pointRadius: connectionData.map((val, idx) =>
+					idx === values.length ? 8 : 0
+				),
+				pointHoverRadius: connectionData.map((val, idx) =>
+					idx === values.length ? 10 : 0
+				),
+				pointStyle: connectionData.map((val, idx) =>
+					idx === values.length ? 'rectRot' : 'circle'
+				),
+				tension: 0.2
+			});
+		}
+
 		chartInstance = new Chart(chartCanvas, {
 			type: 'line',
 			data: {
-				labels,
-				datasets: [
-					{
-						label: `Portfolio Value (${currency})`,
-						data: values,
-						fill: true,
-						backgroundColor: gradientFill,
-						borderColor: 'rgb(59, 130, 246)',
-						borderWidth: 3,
-						pointBackgroundColor: 'rgb(59, 130, 246)',
-						pointBorderColor: 'white',
-						pointBorderWidth: 2,
-						pointRadius: 5,
-						pointHoverRadius: 7,
-						tension: 0.4
-					}
-				]
+				labels: chartLabels,
+				datasets: datasets
 			},
 			options: {
 				responsive: true,
@@ -166,27 +215,56 @@
 						borderWidth: 1,
 						cornerRadius: 8,
 						displayColors: false,
+						filter: (tooltipItem) =>
+							!(showCurrentPoint && tooltipItem.datasetIndex === 1 && tooltipItem.dataIndex !== values.length),
 						callbacks: {
 							title: (context) => {
+								if (!context?.[0] || context[0].dataIndex === undefined) return '';
+
 								const idx = context[0].dataIndex;
-								const raw = data[idx];
-								const date = new Date(raw.createdAt);
-								return date.toLocaleDateString('en-US', {
-									year: 'numeric',
-									month: 'long',
-									day: 'numeric'
-								});
+								const isCurrentPoint = showCurrentPoint && idx === chartLabels.length - 1;
+
+								if (isCurrentPoint) {
+									return 'Today (Current)';
+								}
+
+								if (idx < data.length && data[idx]) {
+									const date = new Date(data[idx].createdAt);
+									return date.toLocaleDateString('en-US', {
+										year: 'numeric',
+										month: 'long',
+										day: 'numeric'
+									});
+								}
+
+								return '';
 							},
 							label: (context) => {
+								if (!context?.parsed) return '';
+
 								const value = context.parsed.y;
-								const totalAssets = data[context.dataIndex].totalAssets;
-								return [
-									`Portfolio Value: ${value.toLocaleString('en-US', {
+								const idx = context.dataIndex;
+								const isCurrentPoint = showCurrentPoint && idx === chartLabels.length - 1;
+
+								if (isCurrentPoint) {
+									return `Current Portfolio Value: ${value.toLocaleString('en-US', {
 										style: 'currency',
 										currency: currency
-									})}`,
-									`Total Assets: ${totalAssets}`
-								];
+									})}`;
+								}
+
+								if (idx < data.length && data[idx]) {
+									const totalAssets = data[idx].totalAssets;
+									return [
+										`Portfolio Value: ${value.toLocaleString('en-US', {
+											style: 'currency',
+											currency: currency
+										})}`,
+										`Total Assets: ${totalAssets}`
+									];
+								}
+
+								return '';
 							}
 						}
 					}
@@ -195,15 +273,20 @@
 		});
 	}
 
-	// Reactive statement to fetch data when portfolioId, authToken or currency changes
 	$: if (portfolioId && authToken && currency) {
 		fetchData();
 	}
 
-	// Reactive statement to render chart when data and canvas are both ready
 	$: if (historyData.length > 0 && chartCanvas) {
 		renderChart(historyData);
 	}
+
+	$: if (liveCurrentValue !== null && lastHistoricalValue !== null) {
+		currentValueDifferencePercent = lastHistoricalValue !== 0
+			? ((liveCurrentValue - lastHistoricalValue) / lastHistoricalValue) * 100
+			: 0;
+		showCurrentPoint = Math.abs(liveCurrentValue - lastHistoricalValue) > 0.01;
+	}
 </script>
 
 {#if isLoading}
@@ -232,7 +315,24 @@
 	<div class="w-full max-w-6xl mx-auto bg-white dark:bg-gray-900 p-6 rounded-xl shadow">
 		<h3 class="text-xl font-semibold text-gray-800 dark:text-gray-100 mb-4">
 			Portfolio Value History
-			{#if currentValue !== null}
+			{#if liveCurrentValue !== null}
+				<span class="ml-2 text-green-600 dark:text-green-400 text-lg font-medium">
+					({liveCurrentValue.toLocaleString('en-US', {
+						style: 'currency',
+						currency: currency
+					})}
+					{#if currentValueDifferencePercent !== null}
+						<span
+							class="{currentValueDifferencePercent >= 0
+								? 'text-green-500'
+								: 'text-red-500'} text-sm font-semibold ml-1 align-middle"
+						>
+							{currentValueDifferencePercent >= 0 ? '+' : ''}{currentValueDifferencePercent.toFixed(2)}%
+						</span>
+					{/if}
+					)
+				</span>
+			{:else if currentValue !== null}
 				<span class="ml-2 text-blue-500 dark:text-blue-300 text-lg font-medium">
 					({currentValue.toLocaleString('en-US', {
 						style: 'currency',

+ 1 - 1
src/routes/portfolio/+page.svelte

@@ -405,7 +405,7 @@
 	<!-- Portfolio Value History Chart -->
 	{#if portfolioId && authToken}
 		<div in:fade class="mt-6">
-			<PortfolioValueHistory {portfolioId} {authToken} {currency} />
+			<PortfolioValueHistory {portfolioId} {authToken} {currency} liveCurrentValue={totalValue} />
 		</div>
 	{/if}