Changeset 42da64d for frontend/src/pages/Dashboard
- Timestamp:
- 05/13/26 00:35:13 (8 weeks ago)
- Branches:
- master
- Children:
- 447c39f
- Parents:
- a8381b1
- Location:
- frontend/src/pages/Dashboard
- Files:
-
- 3 edited
-
DashboardLayout.jsx (modified) (2 diffs)
-
pages/Investing/InvestingTracking.jsx (modified) (1 diff)
-
pages/Investing/components/InvestingProgressCard.jsx (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
frontend/src/pages/Dashboard/DashboardLayout.jsx
ra8381b1 r42da64d 6 6 import DashboardSidebar from "./components/DashboardSidebar.jsx"; 7 7 import DashboardTopbar from "./components/DashboardTopbar.jsx"; 8 import { clearAuthSession } from "../../utils/authSession"; 8 9 9 10 const DashboardLayout = () => { … … 22 23 23 24 const onLogout = () => { 24 localStorage.removeItem("authToken"); 25 localStorage.removeItem("authUser"); 25 clearAuthSession(); 26 26 navigate("/", { replace: true }); 27 27 }; -
frontend/src/pages/Dashboard/pages/Investing/InvestingTracking.jsx
ra8381b1 r42da64d 214 214 onAddAsset={() => navigate("/dashboard/investing/assets/new")} 215 215 /> 216 <InvestingProgressCard assets={graphAssets} />216 <InvestingProgressCard assets={graphAssets} quotesBySymbol={quotesBySymbol} /> 217 217 218 218 {quotesError ? ( -
frontend/src/pages/Dashboard/pages/Investing/components/InvestingProgressCard.jsx
ra8381b1 r42da64d 3 3 import TimeRangeToggle from "../../../../../components/graphs/TimeRangeToggle.jsx"; 4 4 import PercentChangeAreaChart from "../../../../../components/graphs/PercentChangeAreaChart.jsx"; 5 import { percentChangeSeries, sumByTimeBucket } from "../../../../../utils/timeSeries.js"; 6 7 export default function InvestingProgressCard({ assets }) { 5 import { formatBucketLabel } from "../../../../../utils/timeSeries.js"; 6 7 function bucketKey(date, granularity) { 8 const d = new Date(date); 9 if (Number.isNaN(d.getTime())) return null; 10 11 function startOfDay(d) { 12 return new Date(d.getFullYear(), d.getMonth(), d.getDate()); 13 } 14 15 function startOfISOWeek(d) { 16 const date = startOfDay(d); 17 const day = date.getDay(); 18 const diff = (day === 0 ? -6 : 1) - day; 19 date.setDate(date.getDate() + diff); 20 return startOfDay(date); 21 } 22 23 function startOfMonth(d) { 24 return new Date(d.getFullYear(), d.getMonth(), 1); 25 } 26 27 function startOfYear(d) { 28 return new Date(d.getFullYear(), 0, 1); 29 } 30 31 let s; 32 switch (granularity) { 33 case "daily": 34 s = startOfDay(d); 35 break; 36 case "weekly": 37 s = startOfISOWeek(d); 38 break; 39 case "monthly": 40 s = startOfMonth(d); 41 break; 42 case "yearly": 43 s = startOfYear(d); 44 break; 45 default: 46 s = startOfDay(d); 47 } 48 return s.getTime(); 49 } 50 51 export default function InvestingProgressCard({ assets, quotesBySymbol = {} }) { 8 52 const [range, setRange] = useState("weekly"); 9 53 10 54 const points = useMemo(() => { 11 // Option B: "net invested momentum". 12 // Since we only have assets (with buy date/price/quantity) and no transaction history, 13 // we treat each asset as a one-time investment at buyDate. 14 const buckets = sumByTimeBucket( 15 assets, 16 range, 17 (a) => a.buyDate, 18 (a) => (Number(a?.quantity) || 0) * (Number(a?.buyPrice) || 0), 19 ); 20 return percentChangeSeries(buckets); 21 }, [assets, range]); 55 // Calculate portfolio percentage gain/loss over time using current prices 56 // Display cumulative portfolio value and percentage change between time buckets 57 58 if (!assets || assets.length === 0) { 59 return []; 60 } 61 62 // Group assets by buy date bucket 63 const buckets = new Map(); 64 for (const asset of assets) { 65 if (asset?.buyDate) { 66 const ts = bucketKey(asset.buyDate, range); 67 if (ts !== null) { 68 if (!buckets.has(ts)) { 69 buckets.set(ts, []); 70 } 71 buckets.get(ts).push(asset); 72 } 73 } 74 } 75 76 // Sort buckets chronologically 77 const sortedBuckets = Array.from(buckets.entries()).sort((a, b) => a[0] - b[0]); 78 79 // Calculate cumulative portfolio value at each bucket 80 const points = []; 81 let cumulativeAssets = []; 82 83 for (const [ts, assetsInBucket] of sortedBuckets) { 84 cumulativeAssets = [...cumulativeAssets, ...assetsInBucket]; 85 86 let totalInvested = 0; 87 let totalCurrentValue = 0; 88 89 for (const asset of cumulativeAssets) { 90 const qty = Number(asset?.quantity) || 0; 91 const buyPrice = Number(asset?.buyPrice) || 0; 92 const ticker = String(asset?.tickerSymbol || "").toUpperCase(); 93 const currentPrice = quotesBySymbol[ticker] || buyPrice; 94 95 totalInvested += qty * buyPrice; 96 totalCurrentValue += qty * currentPrice; 97 } 98 99 if (totalInvested > 0) { 100 points.push({ 101 ts, 102 invested: totalInvested, 103 current: totalCurrentValue, 104 }); 105 } 106 } 107 108 // Calculate percentage gain from invested to current value 109 const result = []; 110 for (const p of points) { 111 const percentageGain = ((p.current - p.invested) / p.invested) * 100; 112 result.push({ 113 ts: p.ts, 114 value: percentageGain, 115 base: p.current, 116 invested: p.invested, 117 }); 118 } 119 120 return result; 121 }, [assets, range, quotesBySymbol]); 22 122 23 123 const latest = points?.length ? points[points.length - 1] : null; 24 124 const latestPct = latest ? Number(latest.value ?? 0) : 0; 25 const latestBase = latest ? Number(latest.base ?? 0) : 0; 125 const latestCurrentValue = latest ? Number(latest.base ?? 0) : 0; 126 const latestInvested = latest ? Number(latest.invested ?? 0) : 0; 127 128 const totalCurrentPortfolioValue = assets.reduce((sum, asset) => { 129 const qty = Number(asset?.quantity) || 0; 130 const ticker = String(asset?.tickerSymbol || "").toUpperCase(); 131 const currentPrice = quotesBySymbol[ticker] || Number(asset?.buyPrice) || 0; 132 return sum + qty * currentPrice; 133 }, 0); 134 135 const totalInvestedAmount = assets.reduce((sum, asset) => { 136 const qty = Number(asset?.quantity) || 0; 137 const buyPrice = Number(asset?.buyPrice) || 0; 138 return sum + qty * buyPrice; 139 }, 0); 26 140 27 141 return ( … … 29 143 <div className="card-body"> 30 144 <div className="flex items-center justify-between gap-4"> 31 <h2 className="card-title">P rogress</h2>145 <h2 className="card-title">Portfolio Value</h2> 32 146 <div className="flex items-center gap-3"> 33 <span className="badge badge-ghost"> 34 {latestPct >= 0 ? "+" : ""} 35 {latestPct.toFixed(1)}% (last) 36 </span> 147 <div className="flex flex-col items-end gap-1"> 148 <span className="text-sm font-semibold"> 149 ${totalCurrentPortfolioValue.toFixed(2)} 150 </span> 151 <span 152 className={`text-xs font-bold ${ 153 latestPct >= 0 ? "text-success" : "text-error" 154 }`} 155 > 156 {latestPct >= 0 ? "+" : ""} 157 {latestPct.toFixed(2)}% 158 </span> 159 </div> 37 160 <TimeRangeToggle value={range} onChange={setRange} /> 38 161 </div> … … 40 163 41 164 <div className="mt-4 w-full overflow-hidden rounded-xl border border-base-300 bg-base-100 p-2"> 42 {points.length < 2? (165 {points.length < 1 ? ( 43 166 <div className="flex h-65 items-center justify-center text-sm opacity-70"> 44 Add at least 2 investments to see % change.167 Add investments to see portfolio growth. 45 168 </div> 46 169 ) : ( … … 49 172 granularity={range} 50 173 height={260} 51 positiveColor="#1 4b8a6"174 positiveColor="#10b981" 52 175 /> 53 176 )} 54 177 </div> 55 178 56 <div className="mt-2 text-xs opacity-70"> 57 Showing percentage change in invested amount per {range} bucket. Latest bucket invested: {Math.round(latestBase)}. 179 <div className="mt-3 grid grid-cols-3 gap-2 text-xs"> 180 <div className="rounded bg-base-300 p-2"> 181 <div className="opacity-70">Invested</div> 182 <div className="font-semibold">${totalInvestedAmount.toFixed(2)}</div> 183 </div> 184 <div className="rounded bg-base-300 p-2"> 185 <div className="opacity-70">Current Value</div> 186 <div className="font-semibold">${totalCurrentPortfolioValue.toFixed(2)}</div> 187 </div> 188 <div className="rounded bg-base-300 p-2"> 189 <div className="opacity-70">Gain/Loss</div> 190 <div 191 className={`font-semibold ${ 192 totalCurrentPortfolioValue >= totalInvestedAmount 193 ? "text-success" 194 : "text-error" 195 }`} 196 > 197 ${(totalCurrentPortfolioValue - totalInvestedAmount).toFixed(2)} 198 </div> 199 </div> 58 200 </div> 59 201 </div>
Note:
See TracChangeset
for help on using the changeset viewer.
