Skip to content

Commit

Permalink
Pool performance chart
Browse files Browse the repository at this point in the history
  • Loading branch information
kattylucy committed Sep 10, 2024
1 parent 202239e commit 0b56e05
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 119 deletions.
128 changes: 100 additions & 28 deletions centrifuge-app/src/components/Charts/PoolPerformanceChart.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AnchorButton, Box, Grid, IconDownload, Shelf, Stack, Text } from '@centrifuge/fabric'
import { AnchorButton, Box, IconDownload, Select, Shelf, Stack, Tabs, TabsItem, Text } from '@centrifuge/fabric'
import * as React from 'react'
import { useParams } from 'react-router'
import { Bar, CartesianGrid, ComposedChart, Line, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts'
Expand All @@ -15,7 +15,15 @@ import { getRangeNumber } from './utils'
type ChartData = {
day: Date
nav: number
price: number | null
juniorTokenPrice?: number
seniorTokenPrice?: number
price: number
currency?: string
}

type Tranche = {
seniority: number
tokenPrice: number
}

const RangeFilterButton = styled(Stack)`
Expand All @@ -24,15 +32,31 @@ const RangeFilterButton = styled(Stack)`
}
`

const StyledTabs = styled(Tabs)`

Check warning on line 35 in centrifuge-app/src/components/Charts/PoolPerformanceChart.tsx

View workflow job for this annotation

GitHub Actions / build-app

'StyledTabs' is assigned a value but never used

Check warning on line 35 in centrifuge-app/src/components/Charts/PoolPerformanceChart.tsx

View workflow job for this annotation

GitHub Actions / ff-prod / build-app

'StyledTabs' is assigned a value but never used
padding: 8px;
`

const rangeFilters = [
{ value: '30d', label: '30 days' },
{ value: '90d', label: '90 days' },
{ value: 'ytd', label: 'Year to date' },
{ value: 'all', label: 'All' },
] as const

function calculateTranchePrices(pool: any) {
if (!pool?.tranches) return { juniorPrice: null, mezzaninePrice: null, seniorPrice: null }

const juniorTranche = pool.tranches.find((t: Tranche) => t.seniority === 0)
const seniorTranche = pool.tranches.find((t: Tranche) => t.seniority === 1)

const juniorTokenPrice = juniorTranche ? Number(formatBalance(juniorTranche.tokenPrice, undefined, 5, 5)) : null
const seniorTokenPrice = seniorTranche ? Number(formatBalance(seniorTranche.tokenPrice, undefined, 5, 5)) : null

return { juniorTokenPrice, seniorTokenPrice }
}
function PoolPerformanceChart() {
const theme = useTheme()
const [selectedTabIndex, setSelectedTabIndex] = React.useState(0)
const chartColor = theme.colors.accentPrimary
const { pid: poolId } = useParams<{ pid: string }>()

Expand Down Expand Up @@ -69,6 +93,8 @@ function PoolPerformanceChart() {
? formatBalance(pool?.tranches[pool.tranches.length - 1].tokenPrice || 0, undefined, 5, 5)
: null

const trancheTodayPrice = calculateTranchePrices(pool)

const data: ChartData[] = React.useMemo(
() =>
truncatedPoolStates?.map((day) => {
Expand All @@ -85,6 +111,8 @@ function PoolPerformanceChart() {
const today = {
nav: todayAssetValue,
price: todayPrice,
currency: pool.currency.symbol,
...trancheTodayPrice,
}

const chartData = data.slice(-rangeNumber)
Expand Down Expand Up @@ -137,24 +165,34 @@ function PoolPerformanceChart() {
return result
}

console.log(rangeFilters)

return (
<Stack gap={2}>
<Stack flexDirection="row" justifyContent="space-between">
<Text fontSize="18px" fontWeight="500">
<Stack gap={2} padding={20}>
<Stack flexDirection="row" justifyContent="space-between" alignItems="center" mb={12}>
<Text variant="body2" fontWeight="500">
Pool performance
</Text>
<Tabs selectedIndex={selectedTabIndex} onChange={(index) => setSelectedTabIndex(index)}>
<TabsItem styleOverrides={{ padding: '8px' }} showBorder>
Price
</TabsItem>
<TabsItem styleOverrides={{ padding: '8px' }} showBorder>
APY
</TabsItem>
</Tabs>
<AnchorButton
download={`pool-${poolId}-timeseries.csv`}
href={dataUrl}
variant="secondary"
variant="inverted"
icon={IconDownload}
small
>
Download
</AnchorButton>
</Stack>
<Stack>
<CustomLegend data={today} />
<CustomLegend selectedTabIndex={selectedTabIndex} data={today} />
<Shelf justifyContent="flex-end">
{chartData.length > 0 &&
rangeFilters.map((rangeFilter, index) => (
Expand Down Expand Up @@ -255,35 +293,69 @@ function PoolPerformanceChart() {

function CustomLegend({
data,
selectedTabIndex,
}: {
data: {
currency: string
nav: number
price: number | null
juniorTokenPrice: number
seniorTokenPrice?: number | null
}
selectedTabIndex: number
}) {
const theme = useTheme()

Check warning on line 306 in centrifuge-app/src/components/Charts/PoolPerformanceChart.tsx

View workflow job for this annotation

GitHub Actions / build-app

'theme' is assigned a value but never used

Check warning on line 306 in centrifuge-app/src/components/Charts/PoolPerformanceChart.tsx

View workflow job for this annotation

GitHub Actions / ff-prod / build-app

'theme' is assigned a value but never used

const Dot = ({ color }: { color: string }) => (
<Box width="8px" height="8px" borderRadius="50%" backgroundColor={color} marginRight="4px" />
)

const buildData = [
{
color: 'backgroundTertiary',
label: `NAV ${data.currency}`,
value: formatBalance(data.nav),
type: 'nav',
},
{
color: 'textGold',
label: 'Junior token price',
value: data.juniorTokenPrice ?? 0,
type: 'singleTrancheTokenPrice',
},
{
color: 'textPrimary',
label: 'Senior token price',
value: data.seniorTokenPrice ?? 0,
type: 'singleTrancheTokenPrice',
},
]

const accordionData = [
{ label: '30 days', value: '30d' },
{ label: '90 days', value: '90d' },
{ label: 'Year to date', value: 'ytd' },
{ label: 'All', value: 'all' },
]

return (
<Shelf bg="backgroundPage" width="100%" gap={2}>
<Grid pb={2} gridTemplateColumns="fit-content(100%) fit-content(100%) fit-content(100%)" width="100%" gap={8}>
<Stack
borderLeftWidth="3px"
pl={1}
borderLeftStyle="solid"
borderLeftColor={theme.colors.accentPrimary}
gap="4px"
>
<Tooltips type="nav" />
<Text variant="body1">{formatBalance(data.nav, 'USD')}</Text>
</Stack>
{data.price && (
<Stack borderLeftWidth="3px" pl={1} borderLeftStyle="solid" borderLeftColor="#FFC012" gap="4px">
<Tooltips type="singleTrancheTokenPrice" />
<Text variant="body1">{data.price ? formatBalance(data.price, 'USD', 6) : '-'}</Text>
</Stack>
)}
</Grid>
</Shelf>
<Box display="flex" justifyContent="space-between">
<Box display="flex" justifyContent="space-evenly">
{buildData.map((item, index) => {
return (
<Stack key={index} pl={1} display="flex" marginRight="20px">
<Box display="flex" alignItems="center">
<Dot color={item.color} />
<Tooltips type={item.type} label={item.label} />
</Box>
<Text variant="heading1">{item.value}</Text>
</Stack>
)
})}
</Box>
<Box>
<Select options={accordionData} />
</Box>
</Box>
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Spinner } from '../Spinner'

export const PoolPerformance = () => (
<React.Suspense fallback={<Spinner style={{ height: 400 }} />}>
<Card p={3} height={400}>
<Card height={400}>
<Stack gap={2}>
<PoolPerformanceChart />
</Stack>
Expand Down
154 changes: 78 additions & 76 deletions centrifuge-app/src/pages/Pool/Overview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,93 +116,95 @@ export function PoolDetailOverview() {

return (
<FullHeightLayoutSection bg={theme.colors.backgroundSecondary} pt={2} pb={4}>
<Grid height="fit-content" gridTemplateColumns={['1fr', '1fr', '66fr minmax(275px, 33fr)']} gap={[2, 2, 3]}>
<PoolPerformance />
<React.Suspense fallback={<Spinner />}>
<KeyMetrics
assetType={metadata?.pool?.asset}
averageMaturity={averageMaturity}
loans={loans}
poolId={poolId}
/>
</React.Suspense>
</Grid>
{tokens.length > 0 && (
<Box marginX={40} marginY={40}>
<Grid height="fit-content" gridTemplateColumns={['1fr', '1fr', '66fr minmax(275px, 33fr)']} gap={[2, 2, 3]}>
<PoolPerformance />
<React.Suspense fallback={<Spinner />}>
<KeyMetrics
assetType={metadata?.pool?.asset}
averageMaturity={averageMaturity}
loans={loans}
poolId={poolId}
/>
</React.Suspense>
</Grid>
{tokens.length > 0 && (
<React.Suspense fallback={<Spinner />}>
<TrancheTokenCards
trancheTokens={tokens}
poolId={poolId}
createdAt={pool.createdAt}
poolCurrencySymbol={pool.currency.symbol}
/>
</React.Suspense>
)}
<React.Suspense fallback={<Spinner />}>
<TrancheTokenCards
trancheTokens={tokens}
poolId={poolId}
createdAt={pool.createdAt}
poolCurrencySymbol={pool.currency.symbol}
/>
</React.Suspense>
)}
<React.Suspense fallback={<Spinner />}>
{metadata?.pool?.reports?.length || !isTinlakePool ? (
<Card p={3}>
<Grid columns={[1, 2]} equalColumns gap={9} rowGap={3}>
<Stack gap={2}>
<Box display="flex" flexDirection="row" alignItems="center">
<IconFileText />
<Text style={{ marginLeft: 8 }} variant="heading2">
Reports
</Text>
</Box>
<ReportDetails metadata={metadata} />
</Stack>
{metadata?.pool?.reports?.length || !isTinlakePool ? (
<Card p={3}>
<Grid columns={[1, 2]} equalColumns gap={9} rowGap={3}>
<Stack gap={2}>
<Box display="flex" flexDirection="row" alignItems="center">
<IconFileText />
<Text style={{ marginLeft: 8 }} variant="heading2">
Reports
</Text>
</Box>
<ReportDetails metadata={metadata} />
</Stack>
<Stack gap={2}>
<Text variant="heading2">Issuer details</Text>
<IssuerDetails metadata={metadata} />
</Stack>
</Grid>
</Card>
) : null}
{isTinlakePool && (
<Card p={3}>
<Stack gap={2}>
<Text variant="heading2">Issuer details</Text>
<IssuerDetails metadata={metadata} />
</Stack>
</Grid>
</Card>
) : null}
{isTinlakePool && (
<Card p={3}>
<Stack gap={2}>
<Text variant="heading2">Issuer details</Text>
<IssuerDetails metadata={metadata} />
</Stack>
</Card>
)}
</React.Suspense>
{!isTinlakePool && (
<>
<Grid height="fit-content" gridTemplateColumns={['1fr', '1fr', '1fr 1fr']} gap={[2, 2, 3]}>
<React.Suspense fallback={<Spinner />}>
<PoolStructure
numOfTranches={pool.tranches.length}
poolId={poolId}
poolStatus={metadata?.pool?.status}
poolFees={
metadata?.pool?.poolFees?.map((fee) => {
return {
fee: poolFees?.find((f) => f.id === fee.id)?.amounts.percentOfNav ?? Rate.fromFloat(0),
name: fee.name,
id: fee.id,
}
}) || []
}
/>
</React.Suspense>
{/* <React.Suspense fallback={<Spinner />}>
</Card>
)}
</React.Suspense>
{!isTinlakePool && (
<>
<Grid height="fit-content" gridTemplateColumns={['1fr', '1fr', '1fr 1fr']} gap={[2, 2, 3]}>
<React.Suspense fallback={<Spinner />}>
<PoolStructure
numOfTranches={pool.tranches.length}
poolId={poolId}
poolStatus={metadata?.pool?.status}
poolFees={
metadata?.pool?.poolFees?.map((fee) => {
return {
fee: poolFees?.find((f) => f.id === fee.id)?.amounts.percentOfNav ?? Rate.fromFloat(0),
name: fee.name,
id: fee.id,
}
}) || []
}
/>
</React.Suspense>
{/* <React.Suspense fallback={<Spinner />}>
<AssetsByMaturity />
</React.Suspense> */}
</Grid>
{isMedium && (
</Grid>
{isMedium && (
<React.Suspense fallback={<Spinner />}>
<Card p={3}>
<Cashflows />
</Card>
</React.Suspense>
)}
<React.Suspense fallback={<Spinner />}>
<Card p={3}>
<Cashflows />
<TransactionHistory poolId={poolId} />
</Card>
</React.Suspense>
)}
<React.Suspense fallback={<Spinner />}>
<Card p={3}>
<TransactionHistory poolId={poolId} />
</Card>
</React.Suspense>
</>
)}
</>
)}
</Box>
</FullHeightLayoutSection>
)
}
Expand Down
Loading

0 comments on commit 0b56e05

Please sign in to comment.