mango-v4-ui/components/account/HealthContributions.tsx

332 lines
12 KiB
TypeScript
Raw Normal View History

2023-07-06 20:45:32 -07:00
import HealthContributionsChart from './HealthContributionsChart'
import useMangoGroup from 'hooks/useMangoGroup'
import useMangoAccount from 'hooks/useMangoAccount'
2023-12-14 13:27:29 -08:00
import { useCallback, useMemo, useState } from 'react'
2023-07-10 23:01:22 -07:00
import {
ArrowLeftIcon,
2023-07-11 06:14:44 -07:00
NoSymbolIcon,
2023-07-10 23:01:22 -07:00
QuestionMarkCircleIcon,
} from '@heroicons/react/20/solid'
2023-07-10 04:56:38 -07:00
import Tooltip from '@components/shared/Tooltip'
import TokenLogo from '@components/shared/TokenLogo'
import { useTranslation } from 'next-i18next'
import MarketLogos from '@components/trade/MarketLogos'
2023-07-10 23:01:22 -07:00
import mangoStore from '@store/mangoStore'
2023-07-11 04:00:06 -07:00
import TokensHealthTable from './TokensHealthTable'
import MarketsHealthTable from './MarketsHealthTable'
2023-07-14 06:34:09 -07:00
import { HealthContribution, PerpMarketContribution } from 'types'
2023-07-20 19:08:34 -07:00
import useHealthContributions from 'hooks/useHealthContributions'
2023-07-06 20:45:32 -07:00
const HealthContributions = ({ hideView }: { hideView: () => void }) => {
2023-07-10 05:39:50 -07:00
const { t } = useTranslation(['common', 'account', 'trade'])
2023-07-06 20:45:32 -07:00
const { group } = useMangoGroup()
2023-07-11 06:14:44 -07:00
const { mangoAccount, mangoAccountAddress } = useMangoAccount()
2023-07-10 23:01:22 -07:00
const [initActiveIndex, setInitActiveIndex] = useState<number | undefined>(
2023-07-21 11:47:53 -07:00
undefined,
2023-07-10 23:01:22 -07:00
)
const [maintActiveIndex, setMaintActiveIndex] = useState<number | undefined>(
2023-07-21 11:47:53 -07:00
undefined,
2023-07-10 23:01:22 -07:00
)
2023-07-20 19:08:34 -07:00
const { initContributions, maintContributions } = useHealthContributions()
2023-07-06 20:45:32 -07:00
const [initHealthContributions, maintHealthContributions] = useMemo(() => {
if (!group || !mangoAccount) return [[], []]
2023-07-20 19:08:34 -07:00
const initHealthContributions = []
for (const item of initContributions) {
2023-07-14 06:34:09 -07:00
const contribution = item.contribution
if (item.asset === 'USDC') {
const hasPerp =
!!item.contributionDetails?.perpMarketContributions.find(
2023-07-21 11:47:53 -07:00
(perp: PerpMarketContribution) => Math.abs(perp.contributionUi) > 0,
2023-07-14 06:34:09 -07:00
)
2023-07-20 19:08:34 -07:00
initHealthContributions.push({
2023-07-14 06:47:28 -07:00
...item,
2023-07-18 03:53:43 -07:00
contribution: Math.abs(contribution),
2023-07-14 06:34:09 -07:00
hasPerp: hasPerp,
isAsset: contribution > 0 ? true : false,
})
if (item.contributionDetails) {
for (const perpMarket of item.contributionDetails
.perpMarketContributions) {
const contribution = Math.abs(perpMarket.contributionUi)
if (contribution > 0) {
2023-07-20 19:08:34 -07:00
initHealthContributions.push({
2023-07-14 06:34:09 -07:00
asset: perpMarket.market,
contribution: contribution,
isAsset: perpMarket.contributionUi > 0 ? true : false,
})
}
}
}
} else {
2023-07-20 19:08:34 -07:00
initHealthContributions.push({
2023-07-14 06:34:09 -07:00
...item,
2023-07-14 06:47:28 -07:00
isAsset: contribution > 0 ? true : false,
contribution: Math.abs(contribution),
2023-07-14 06:34:09 -07:00
})
}
}
2023-07-20 19:08:34 -07:00
const maintHealthContributions = []
for (const item of maintContributions) {
2023-07-14 06:34:09 -07:00
const contribution = item.contribution
if (item.asset === 'USDC') {
const hasPerp =
!!item.contributionDetails?.perpMarketContributions.find(
2023-07-21 11:47:53 -07:00
(perp: PerpMarketContribution) => Math.abs(perp.contributionUi) > 0,
2023-07-14 06:34:09 -07:00
)
2023-07-20 19:08:34 -07:00
maintHealthContributions.push({
2023-07-18 03:53:43 -07:00
...item,
2023-07-14 06:34:09 -07:00
hasPerp: hasPerp,
isAsset: contribution > 0 ? true : false,
2023-07-18 03:53:43 -07:00
contribution: Math.abs(contribution),
2023-07-14 06:34:09 -07:00
})
if (item.contributionDetails) {
for (const perpMarket of item.contributionDetails
.perpMarketContributions) {
const contribution = Math.abs(perpMarket.contributionUi)
if (contribution > 0) {
2023-07-20 19:08:34 -07:00
maintHealthContributions.push({
2023-07-14 06:34:09 -07:00
asset: perpMarket.market,
contribution: contribution,
isAsset: perpMarket.contributionUi > 0 ? true : false,
})
}
}
}
} else {
2023-07-20 19:08:34 -07:00
maintHealthContributions.push({
2023-07-14 06:34:09 -07:00
...item,
2023-07-14 06:47:28 -07:00
isAsset: contribution > 0 ? true : false,
contribution: Math.abs(contribution),
2023-07-14 06:34:09 -07:00
})
}
}
2023-07-20 19:08:34 -07:00
return [initHealthContributions, maintHealthContributions]
}, [group, mangoAccount, initContributions, maintContributions])
2023-07-06 20:45:32 -07:00
2023-07-10 04:56:38 -07:00
const [initHealthMarkets, initHealthTokens] = useMemo(() => {
if (!initHealthContributions.length) return [[], []]
const splitData = initHealthContributions.reduce(
(
acc: { market: HealthContribution[]; token: HealthContribution[] },
2023-07-21 11:47:53 -07:00
obj: HealthContribution,
2023-07-10 04:56:38 -07:00
) => {
2023-07-14 06:47:28 -07:00
const isPerp = obj.asset.includes('PERP')
const isSpotMarket = obj.asset.includes('/')
if (isSpotMarket) {
2023-07-10 04:56:38 -07:00
acc.market.push(obj)
2023-07-14 06:34:09 -07:00
}
2023-07-14 06:47:28 -07:00
if (!isPerp && !isSpotMarket) {
2023-07-10 04:56:38 -07:00
acc.token.push(obj)
}
return acc
},
2023-07-21 11:47:53 -07:00
{ market: [], token: [] },
2023-07-10 04:56:38 -07:00
)
return [splitData.market, splitData.token]
}, [initHealthContributions])
2023-07-06 20:45:32 -07:00
2023-07-10 04:56:38 -07:00
const [maintHealthMarkets, maintHealthTokens] = useMemo(() => {
if (!maintHealthContributions.length) return [[], []]
const splitData = maintHealthContributions.reduce(
(
acc: { market: HealthContribution[]; token: HealthContribution[] },
2023-07-21 11:47:53 -07:00
obj: HealthContribution,
2023-07-10 04:56:38 -07:00
) => {
2023-07-14 06:47:28 -07:00
const isPerp = obj.asset.includes('PERP')
const isSpotMarket = obj.asset.includes('/')
if (isSpotMarket) {
2023-07-10 04:56:38 -07:00
acc.market.push(obj)
2023-07-14 06:34:09 -07:00
}
2023-07-14 06:47:28 -07:00
if (!isPerp && !isSpotMarket) {
2023-07-10 04:56:38 -07:00
acc.token.push(obj)
}
return acc
},
2023-07-21 11:47:53 -07:00
{ market: [], token: [] },
2023-07-10 04:56:38 -07:00
)
2023-07-10 06:02:29 -07:00
const markets = splitData.market.filter((d) => d.contribution > 0)
const tokens = splitData.token
return [markets, tokens]
2023-07-10 04:56:38 -07:00
}, [maintHealthContributions])
2023-07-10 23:01:22 -07:00
const renderLegendLogo = (asset: string) => {
const group = mangoStore.getState().group
if (!group)
return <QuestionMarkCircleIcon className="h-6 w-6 text-th-fgd-3" />
const isSpotMarket = asset.includes('/')
2023-07-14 06:34:09 -07:00
const isPerpMarket = asset.includes('PERP')
const isMarket = isSpotMarket || isPerpMarket
if (isMarket) {
let market
if (isSpotMarket) {
market = group.getSerum3MarketByName(asset)
} else {
market = group.getPerpMarketByName(asset)
}
2023-07-10 23:01:22 -07:00
return market ? (
<MarketLogos market={market} size="small" />
) : (
<QuestionMarkCircleIcon className="h-6 w-6 text-th-fgd-3" />
)
} else {
const bank = group.banksMapByName.get(asset)?.[0]
return bank ? (
<div className="mr-1.5">
<TokenLogo bank={bank} size={16} />
</div>
) : (
<QuestionMarkCircleIcon className="h-6 w-6 text-th-fgd-3" />
)
}
}
const initChartData = useMemo(() => {
if (!initHealthContributions.length) return []
return initHealthContributions
.filter((cont) => {
2023-07-17 03:05:23 -07:00
if (cont.asset.includes('PERP')) {
return
} else if (cont.asset.includes('/')) {
2023-07-10 23:01:22 -07:00
return cont.contribution > 0.01
} else return cont
})
.sort((a, b) => {
const aMultiplier = a.isAsset ? 1 : -1
const bMultiplier = b.isAsset ? 1 : -1
return b.contribution * bMultiplier - a.contribution * aMultiplier
})
}, [initHealthContributions])
const maintChartData = useMemo(() => {
if (!maintHealthContributions.length) return []
return maintHealthContributions
.filter((cont) => {
2023-07-17 03:05:23 -07:00
if (cont.asset.includes('PERP')) {
return
} else if (cont.asset.includes('/')) {
2023-07-10 23:01:22 -07:00
return cont.contribution > 0.01
} else return cont
})
.sort((a, b) => {
const aMultiplier = a.isAsset ? 1 : -1
const bMultiplier = b.isAsset ? 1 : -1
return b.contribution * bMultiplier - a.contribution * aMultiplier
})
}, [maintHealthContributions])
2023-12-14 13:27:29 -08:00
const handleLegendEnter = useCallback(
(item: HealthContribution) => {
const maintIndex = maintChartData.findIndex((d) => d.asset === item.asset)
const initIndex = initChartData.findIndex((d) => d.asset === item.asset)
if (maintIndex !== -1) {
setMaintActiveIndex(maintIndex)
}
if (initIndex !== -1) {
setInitActiveIndex(initIndex)
}
},
[initChartData, maintChartData],
)
const handleLegendMouseLeave = () => {
setInitActiveIndex(undefined)
setMaintActiveIndex(undefined)
}
2023-07-11 06:14:44 -07:00
return group ? (
2023-07-06 20:45:32 -07:00
<>
2023-07-10 04:56:38 -07:00
<div className="hide-scroll flex h-14 items-center space-x-4 overflow-x-auto border-b border-th-bkg-3">
2023-07-06 20:45:32 -07:00
<button
className="flex h-14 w-14 shrink-0 items-center justify-center border-r border-th-bkg-3 focus-visible:bg-th-bkg-3 md:hover:bg-th-bkg-2"
2023-07-06 20:45:32 -07:00
onClick={hideView}
>
<ArrowLeftIcon className="h-5 w-5" />
</button>
2023-07-10 23:01:22 -07:00
<h2 className="text-lg">{t('account:health-contributions')}</h2>
2023-07-10 04:56:38 -07:00
</div>
2023-07-11 06:14:44 -07:00
{mangoAccountAddress ? (
<>
<div className="mx-auto grid max-w-[1140px] grid-cols-2 gap-6 p-6 sm:gap-8">
<div className="col-span-1 flex h-full flex-col items-center">
<Tooltip content={t('account:tooltip-init-health')}>
<h3 className="tooltip-underline text-xs sm:text-base">
{t('account:init-health-contributions')}
</h3>
</Tooltip>
<HealthContributionsChart
data={initChartData}
activeIndex={initActiveIndex}
setActiveIndex={setInitActiveIndex}
/>
</div>
<div className="col-span-1 flex flex-col items-center">
<Tooltip content={t('account:tooltip-maint-health')}>
<h3 className="tooltip-underline text-xs sm:text-base">
{t('account:maint-health-contributions')}
</h3>
</Tooltip>
<HealthContributionsChart
data={maintChartData}
activeIndex={maintActiveIndex}
setActiveIndex={setMaintActiveIndex}
/>
</div>
<div className="col-span-2 mx-auto flex max-w-[600px] flex-wrap justify-center space-x-4">
{[...maintChartData]
.sort((a, b) => b.contribution - a.contribution)
.map((d, i) => {
return (
<div
key={d.asset + i}
className={`default-transition flex h-7 cursor-pointer items-center md:hover:text-th-active`}
2023-12-14 13:27:29 -08:00
onClick={() => handleLegendEnter(d)}
onMouseEnter={() => handleLegendEnter(d)}
2023-07-11 06:14:44 -07:00
onMouseLeave={handleLegendMouseLeave}
>
{renderLegendLogo(d.asset)}
<span className={`default-transition`}>{d.asset}</span>
</div>
)
})}
</div>
</div>
{maintHealthTokens.length ? (
<div className="border-t border-th-bkg-3 pt-6">
<h2 className="mb-1 px-6 text-lg">{t('tokens')}</h2>
<TokensHealthTable
initTokens={initHealthTokens}
maintTokens={maintHealthTokens}
2023-12-14 13:27:29 -08:00
handleLegendClick={handleLegendEnter}
handleLegendMouseEnter={handleLegendEnter}
2023-07-11 06:14:44 -07:00
handleLegendMouseLeave={handleLegendMouseLeave}
/>
</div>
) : null}
{maintHealthMarkets.length ? (
<div className="pt-6">
<h2 className="mb-1 px-6 text-lg">{t('markets')}</h2>
<MarketsHealthTable
initMarkets={initHealthMarkets}
maintMarkets={maintHealthMarkets}
2023-12-14 13:27:29 -08:00
handleLegendClick={handleLegendEnter}
handleLegendMouseEnter={handleLegendEnter}
2023-07-11 06:14:44 -07:00
handleLegendMouseLeave={handleLegendMouseLeave}
/>
</div>
) : null}
</>
) : (
<div className="mx-6 mt-6 flex flex-col items-center rounded-lg border border-th-bkg-3 p-8">
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
<p>{t('account:no-data')}</p>
2023-07-10 23:01:22 -07:00
</div>
2023-07-11 06:14:44 -07:00
)}
2023-07-06 20:45:32 -07:00
</>
2023-07-10 04:56:38 -07:00
) : null
2023-07-06 20:45:32 -07:00
}
export default HealthContributions