import HealthContributionsChart from './HealthContributionsChart' import useMangoGroup from 'hooks/useMangoGroup' import useMangoAccount from 'hooks/useMangoAccount' import { useCallback, useMemo, useState } from 'react' import { ArrowLeftIcon, NoSymbolIcon, QuestionMarkCircleIcon, } from '@heroicons/react/20/solid' import Tooltip from '@components/shared/Tooltip' import TokenLogo from '@components/shared/TokenLogo' import { useTranslation } from 'next-i18next' import MarketLogos from '@components/trade/MarketLogos' import mangoStore from '@store/mangoStore' import TokensHealthTable from './TokensHealthTable' import MarketsHealthTable from './MarketsHealthTable' import { HealthContribution, PerpMarketContribution } from 'types' import useHealthContributions from 'hooks/useHealthContributions' const HealthContributions = ({ hideView }: { hideView: () => void }) => { const { t } = useTranslation(['common', 'account', 'trade']) const { group } = useMangoGroup() const { mangoAccount, mangoAccountAddress } = useMangoAccount() const [initActiveIndex, setInitActiveIndex] = useState( undefined, ) const [maintActiveIndex, setMaintActiveIndex] = useState( undefined, ) const { initContributions, maintContributions } = useHealthContributions() const [initHealthContributions, maintHealthContributions] = useMemo(() => { if (!group || !mangoAccount) return [[], []] const initHealthContributions = [] for (const item of initContributions) { const contribution = item.contribution if (item.asset === 'USDC') { const hasPerp = !!item.contributionDetails?.perpMarketContributions.find( (perp: PerpMarketContribution) => Math.abs(perp.contributionUi) > 0, ) initHealthContributions.push({ ...item, contribution: Math.abs(contribution), 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) { initHealthContributions.push({ asset: perpMarket.market, contribution: contribution, isAsset: perpMarket.contributionUi > 0 ? true : false, }) } } } } else { initHealthContributions.push({ ...item, isAsset: contribution > 0 ? true : false, contribution: Math.abs(contribution), }) } } const maintHealthContributions = [] for (const item of maintContributions) { const contribution = item.contribution if (item.asset === 'USDC') { const hasPerp = !!item.contributionDetails?.perpMarketContributions.find( (perp: PerpMarketContribution) => Math.abs(perp.contributionUi) > 0, ) maintHealthContributions.push({ ...item, hasPerp: hasPerp, isAsset: contribution > 0 ? true : false, contribution: Math.abs(contribution), }) if (item.contributionDetails) { for (const perpMarket of item.contributionDetails .perpMarketContributions) { const contribution = Math.abs(perpMarket.contributionUi) if (contribution > 0) { maintHealthContributions.push({ asset: perpMarket.market, contribution: contribution, isAsset: perpMarket.contributionUi > 0 ? true : false, }) } } } } else { maintHealthContributions.push({ ...item, isAsset: contribution > 0 ? true : false, contribution: Math.abs(contribution), }) } } return [initHealthContributions, maintHealthContributions] }, [group, mangoAccount, initContributions, maintContributions]) const [initHealthMarkets, initHealthTokens] = useMemo(() => { if (!initHealthContributions.length) return [[], []] const splitData = initHealthContributions.reduce( ( acc: { market: HealthContribution[]; token: HealthContribution[] }, obj: HealthContribution, ) => { const isPerp = obj.asset.includes('PERP') const isSpotMarket = obj.asset.includes('/') if (isSpotMarket) { acc.market.push(obj) } if (!isPerp && !isSpotMarket) { acc.token.push(obj) } return acc }, { market: [], token: [] }, ) return [splitData.market, splitData.token] }, [initHealthContributions]) const [maintHealthMarkets, maintHealthTokens] = useMemo(() => { if (!maintHealthContributions.length) return [[], []] const splitData = maintHealthContributions.reduce( ( acc: { market: HealthContribution[]; token: HealthContribution[] }, obj: HealthContribution, ) => { const isPerp = obj.asset.includes('PERP') const isSpotMarket = obj.asset.includes('/') if (isSpotMarket) { acc.market.push(obj) } if (!isPerp && !isSpotMarket) { acc.token.push(obj) } return acc }, { market: [], token: [] }, ) const markets = splitData.market.filter((d) => d.contribution > 0) const tokens = splitData.token return [markets, tokens] }, [maintHealthContributions]) const renderLegendLogo = (asset: string) => { const group = mangoStore.getState().group if (!group) return const isSpotMarket = asset.includes('/') const isPerpMarket = asset.includes('PERP') const isMarket = isSpotMarket || isPerpMarket if (isMarket) { let market if (isSpotMarket) { market = group.getSerum3MarketByName(asset) } else { market = group.getPerpMarketByName(asset) } return market ? ( ) : ( ) } else { const bank = group.banksMapByName.get(asset)?.[0] return bank ? (
) : ( ) } } const initChartData = useMemo(() => { if (!initHealthContributions.length) return [] return initHealthContributions .filter((cont) => { if (cont.asset.includes('PERP')) { return } else if (cont.asset.includes('/')) { 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) => { if (cont.asset.includes('PERP')) { return } else if (cont.asset.includes('/')) { 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]) 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) } return group ? ( <>

{t('account:health-contributions')}

{mangoAccountAddress ? ( <>

{t('account:init-health-contributions')}

{t('account:maint-health-contributions')}

{[...maintChartData] .sort((a, b) => b.contribution - a.contribution) .map((d, i) => { return (
handleLegendEnter(d)} onMouseEnter={() => handleLegendEnter(d)} onMouseLeave={handleLegendMouseLeave} > {renderLegendLogo(d.asset)} {d.asset}
) })}
{maintHealthTokens.length ? (

{t('tokens')}

) : null} {maintHealthMarkets.length ? (

{t('markets')}

) : null} ) : (

{t('account:no-data')}

)} ) : null } export default HealthContributions