11<script setup lang="ts">
22import { Doughnut } from ' vue-chartjs'
33import { useAccountBalances } from ' ~/composables/useAccountBalances'
4+ import { useAssetUsdPrices } from ' ~/composables/useAssetUsdPrices'
45import { staticTokens } from ' ~/constants/tokens'
56
67const { balances } = useAccountBalances ()
8+ const { getUsdPerUnit, primeModules } = useAssetUsdPrices ()
79
810// Fixed chart size (same on desktop and mobile) to keep the donut perfectly round
911// Slightly reduced to give more space to the legend
@@ -33,40 +35,44 @@ const nameForModule = (module: string) => {
3335const PALETTE = [' #ef4444' , ' #f97316' , ' #f59e0b' , ' #84cc16' , ' #10b981' , ' #06b6d4' , ' #3b82f6' , ' #8b5cf6' ]
3436const OTHERS_COLOR = ' #4b5563'
3537
36- // Group balances by module (specific chain when viewing All chains), sort by amount desc
38+ // Group balances by USD value; when All Chains selected, aggregate by module across chains.
3739const groupedByAsset = computed (() => {
38- const map = new Map <string , { module: string ; label: string ; amount : number }>()
40+ const map = new Map <string , { module: string ; label: string ; usd : number }>()
3941 for (const b of (balances .value || [])) {
4042 const amount = Number (b ?.balance || 0 )
4143 if (! amount || amount <= 0 ) continue
4244 const baseModule = b .module || ' unknown'
43- const key = isAllChains .value ? ` ${baseModule }|${b .chainId } ` : baseModule
44- const label = isAllChains .value ? ` ${nameForModule (baseModule )} (c${b .chainId }) ` : nameForModule (baseModule )
45+ const unitUsd = getUsdPerUnit (baseModule )
46+ const usd = Number .isFinite (unitUsd ) ? unitUsd * amount : 0
47+ const key = isAllChains .value ? baseModule : ` ${baseModule }|${b .chainId } `
48+ const label = isAllChains .value ? nameForModule (baseModule ) : ` ${nameForModule (baseModule )} (c${b .chainId }) `
4549 const prev = map .get (key )
4650 if (prev ) {
47- prev .amount += amount
51+ prev .usd += usd
4852 } else {
49- map .set (key , { module: key , label , amount })
53+ map .set (key , { module: key , label , usd })
54+ }
55+ if (process .client ) {
56+ console .debug (' [pie] module' , baseModule , ' amount' , amount , ' unitUSD' , unitUsd , ' usd+' , usd , ' key' , key )
5057 }
5158 }
52- return Array .from (map .values ()).sort ((a , b ) => b .amount - a .amount )
59+ return Array .from (map .values ()).sort ((a , b ) => b .usd - a .usd )
5360})
5461
55- // Total based on amounts for now
56- // NOTE: In the future, switch to USD value once pricing is available
57- const totalBalance = computed (() => groupedByAsset .value .reduce ((acc , i ) => acc + Number (i .amount || 0 ), 0 ))
62+ // Total USD
63+ const totalBalance = computed (() => groupedByAsset .value .reduce ((acc , i ) => acc + Number (i .usd || 0 ), 0 ))
5864
5965// Top 8 slices + aggregate others
6066const slices = computed (() => {
6167 const SLICE_CAP = 8
6268 const top = groupedByAsset .value .slice (0 , SLICE_CAP )
6369 if (groupedByAsset .value .length <= SLICE_CAP ) return top
64- const othersAmount = groupedByAsset .value .slice (SLICE_CAP ).reduce ((a , i ) => a + i .amount , 0 )
65- return [... top , { module: ' others' , label: ' OTHERS' , amount : othersAmount }]
70+ const othersAmount = groupedByAsset .value .slice (SLICE_CAP ).reduce ((a , i ) => a + i .usd , 0 )
71+ return [... top , { module: ' others' , label: ' OTHERS' , usd : othersAmount }]
6672})
6773
6874const labels = computed (() => slices .value .map (s => s .label ))
69- const dataValues = computed (() => slices .value .map (s => s .amount ))
75+ const dataValues = computed (() => slices .value .map (s => s .usd ))
7076const backgroundColors = computed (() => slices .value .map ((s , i ) => (s .module === ' others' ? OTHERS_COLOR : PALETTE [i % PALETTE .length ])))
7177
7278const chartData = computed (() => ({
@@ -128,6 +134,7 @@ const externalTooltipHandler = (context: any) => {
128134 tooltipEl .style .padding = ' 8px 10px'
129135 tooltipEl .style .color = ' #fafafa'
130136 tooltipEl .style .fontSize = ' 12px'
137+ tooltipEl .style .whiteSpace = ' normal'
131138 tooltipEl .style .zIndex = ' 50'
132139 chart .canvas .parentNode .appendChild (tooltipEl )
133140 }
@@ -141,14 +148,8 @@ const externalTooltipHandler = (context: any) => {
141148 const index = tooltip .dataPoints [0 ].dataIndex
142149 const label = labels .value [index ]
143150 const value = dataValues .value [index ]
144- // Percentage based on amount for now. Change to USD later when prices are available.
145151 const pct = totalBalance .value > 0 ? ((value / totalBalance .value ) * 100 ).toFixed (2 ) : ' 0.00'
146- tooltipEl .innerHTML = ` <div class="flex items-center gap-2">
147- <div class="w-2.5 h-2.5 rounded-full" style="background:${backgroundColors .value [index ]}"></div>
148- <div class="font-medium">${label }</div>
149- </div>
150- <div class="text-[#bbbbbb] mt-1">Quantity: ${value }</div>
151- <div class="text-[#bbbbbb]">Share: ${pct }%</div> `
152+ tooltipEl .innerHTML = ` <div class=\" flex items-center gap-2\" style=\" white-space:nowrap\" >\n <div class=\" w-2.5 h-2.5 rounded-full\" style=\" background:${backgroundColors .value [index ]}\" ></div>\n <div class=\" font-medium uppercase\" >${label }</div>\n </div>\n <div class=\" text-[#bbbbbb] mt-1\" style=\" white-space:nowrap\" >USD: $${Number (value ).toFixed (2 )}</div>\n <div class=\" text-[#bbbbbb]\" style=\" white-space:nowrap\" >Share: ${pct }%</div> `
152153 }
153154
154155 const { offsetLeft : positionX, offsetTop : positionY } = chart .canvas
@@ -175,11 +176,11 @@ const chartOptions = reactive({
175176
176177<template >
177178 <div class =" w-full" >
178- <template v-if =" totalBalance > 0 " >
179+ <template v-if =" groupedByAsset . length > 0 " >
179180 <div class =" grid grid-cols-1 md:flex md:items-center" >
180181 <!-- Left: Chart (60%) -->
181182 <div class =" md:basis-6/12 md:shrink-0 flex justify-center md:justify-start" >
182- <div class =" relative overflow-hidden " :style =" { width: chartSizePx, height: chartSizePx }" >
183+ <div class =" relative overflow-visible " :style =" { width: chartSizePx, height: chartSizePx }" >
183184 <Doughnut
184185 :data =" chartData"
185186 :options =" chartOptions"
@@ -190,7 +191,7 @@ const chartOptions = reactive({
190191 <div class =" absolute inset-0 flex items-center justify-center pointer-events-none select-none" >
191192 <div class =" text-center" >
192193 <div class =" text-[12px] text-[#bbbbbb]" >Total</div >
193- <div class =" text-[16px] text-white font-semibold" >{{ totalBalance.toLocaleString( ) }}</div >
194+ <div class =" text-[16px] text-white font-semibold" >$ {{ Number( totalBalance).toFixed(2 ) }}</div >
194195 </div >
195196 </div >
196197 </div >
@@ -203,11 +204,11 @@ const chartOptions = reactive({
203204 <div class =" flex items-center gap-3 min-w-0" >
204205 <div class =" w-3 h-3 rounded-full" :style =" { background: (s.module === 'others' ? '#4b5563' : backgroundColors[idx]) }" ></div >
205206 <div class =" truncate text-[#fafafa] text-[12px]" :ref =" el => setLabelRef(el as HTMLElement | null, idx)" >
206- <span class =" text-[#bbbbbb] mr-1" >{{ ((s.amount / (totalBalance || 1)) * 100).toFixed(2) }}%</span >
207+ <span class =" text-[#bbbbbb] mr-1" >{{ ((s.usd / (totalBalance || 1)) * 100).toFixed(2) }}%</span >
207208 <span class =" uppercase" >{{ s.label }}</span >
208209 </div >
209210 </div >
210- <div class =" text-[#fafafa] text-[12px] font-medium" :class =" { 'basis-full mt-1 text-right': shouldWrapAmount[idx] }" >{{ new Intl.NumberFormat('en-US', { maximumFractionDigits: 12 }).format(s.amount ) }}</div >
211+ <div class =" text-[#fafafa] text-[12px] font-medium" :class =" { 'basis-full mt-1 text-right': shouldWrapAmount[idx] }" >$ {{ Number(s.usd).toFixed(2 ) }}</div >
211212 </div >
212213 </div >
213214 </div >
0 commit comments