|
|
@@ -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',
|