From 679ef78a778d15a46441150b31ca4ab2164773cf Mon Sep 17 00:00:00 2001 From: katty barroso Date: Tue, 10 Sep 2024 13:21:22 -0500 Subject: [PATCH] Pool overview grapg changes --- .../Charts/PoolPerformanceChart.tsx | 223 ++++++++++-------- .../src/components/Charts/Tooltip.tsx | 4 +- .../src/pages/Pool/Overview/index.tsx | 4 +- fabric/src/components/Select/index.tsx | 10 +- fabric/src/components/TextInput/index.tsx | 5 +- 5 files changed, 141 insertions(+), 105 deletions(-) diff --git a/centrifuge-app/src/components/Charts/PoolPerformanceChart.tsx b/centrifuge-app/src/components/Charts/PoolPerformanceChart.tsx index e08bcfb30..bcf720fb4 100644 --- a/centrifuge-app/src/components/Charts/PoolPerformanceChart.tsx +++ b/centrifuge-app/src/components/Charts/PoolPerformanceChart.tsx @@ -2,7 +2,7 @@ import { AnchorButton, Box, IconDownload, Select, Shelf, Stack, Tabs, TabsItem, import * as React from 'react' import { useParams } from 'react-router' import { Bar, CartesianGrid, ComposedChart, Line, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts' -import styled, { useTheme } from 'styled-components' +import { useTheme } from 'styled-components' import { getCSVDownloadUrl } from '../../../src/utils/getCSVDownloadUrl' import { daysBetween, formatDate } from '../../utils/date' import { formatBalance, formatBalanceAbbreviated } from '../../utils/formatting' @@ -15,9 +15,8 @@ import { getRangeNumber } from './utils' type ChartData = { day: Date nav: number - juniorTokenPrice?: number - seniorTokenPrice?: number - price: number + juniorTokenPrice: number | null + seniorTokenPrice?: number | null currency?: string } @@ -26,38 +25,29 @@ type Tranche = { tokenPrice: number } -const RangeFilterButton = styled(Stack)` - &:hover { - cursor: pointer; - } -` - -const StyledTabs = styled(Tabs)` - padding: 8px; -` - const rangeFilters = [ + { value: 'all', label: 'All' }, { 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 } + if (!pool?.tranches) return { juniorPrice: null, seniorPrice: null } const juniorTranche = pool.tranches.find((t: Tranche) => t.seniority === 0) - const seniorTranche = pool.tranches.find((t: Tranche) => t.seniority === 1) + const seniorTranche = pool.tranches.length > 1 ? pool.tranches.find((t: Tranche) => t.seniority === 1) : null 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 chartColor = theme.colors.textGold const { pid: poolId } = useParams<{ pid: string }>() if (!poolId) throw new Error('Pool not found') @@ -97,15 +87,37 @@ function PoolPerformanceChart() { const data: ChartData[] = React.useMemo( () => - truncatedPoolStates?.map((day) => { + truncatedPoolStates?.map((day: any) => { const nav = day.poolState.netAssetValue.toDecimal().toNumber() - const price = (isSingleTranche && Object.values(day.tranches)[0].price?.toFloat()) || null + + const trancheKeys = Object.keys(day.tranches) + const juniorTrancheKey = trancheKeys[0] + const seniorTrancheKey = trancheKeys[1] || null + + const juniorTokenPrice = (day.tranches[juniorTrancheKey]?.price as any)?.toFloat() || null + const seniorTokenPrice = seniorTrancheKey + ? (day.tranches[seniorTrancheKey]?.price as any)?.toFloat() || null + : null + if (day.timestamp && new Date(day.timestamp).toDateString() === new Date().toDateString()) { - return { day: new Date(day.timestamp), nav: todayAssetValue, price: Number(todayPrice) } + const tranchePrices = calculateTranchePrices(pool) + + return { + day: new Date(day.timestamp), + nav: todayAssetValue, + juniorTokenPrice: tranchePrices.juniorTokenPrice ?? null, + seniorTokenPrice: tranchePrices.seniorTokenPrice ?? null, + } + } + + return { + day: new Date(day.timestamp), + nav: Number(nav), + juniorTokenPrice, + seniorTokenPrice, } - return { day: new Date(day.timestamp), nav: Number(nav), price: Number(price) } }) || [], - [isSingleTranche, truncatedPoolStates, todayAssetValue, todayPrice] + [isSingleTranche, truncatedPoolStates, todayAssetValue, todayPrice, pool] ) const today = { @@ -122,10 +134,19 @@ function PoolPerformanceChart() { return undefined } - const filteredData = chartData.map((data) => ({ - day: data.day, - tokenPrice: data.price, - })) + const filteredData = chartData.map((data) => { + const base = { + day: data.day, + nav: data.nav, + juniorTokenPrice: data.juniorTokenPrice, + } + if (data.seniorTokenPrice) { + return { + ...base, + seniorTokenPrice: data.seniorTokenPrice, + } + } else return { ...base } + }) return getCSVDownloadUrl(filteredData as any) }, [chartData]) @@ -134,14 +155,19 @@ function PoolPerformanceChart() { if (!chartData) return [0, 100] const min = - chartData?.reduce((prev, curr) => { - return prev.price! < curr.price! ? prev : curr - }, chartData[0])?.price || 0 + chartData.reduce((prev, curr) => { + const currMin = Math.min(curr.juniorTokenPrice ?? Infinity, curr.seniorTokenPrice ?? Infinity) + const prevMin = Math.min(prev.juniorTokenPrice ?? Infinity, prev.seniorTokenPrice ?? Infinity) + return currMin < prevMin ? curr : prev + }, chartData[0])?.juniorTokenPrice ?? 0 const max = - chartData?.reduce((prev, curr) => { - return prev.price! > curr.price! ? prev : curr - }, chartData[0])?.price || 1 + chartData.reduce((prev, curr) => { + const currMax = Math.max(curr.juniorTokenPrice ?? -Infinity, curr.seniorTokenPrice ?? -Infinity) + const prevMax = Math.max(prev.juniorTokenPrice ?? -Infinity, prev.seniorTokenPrice ?? -Infinity) + return currMax > prevMax ? curr : prev + }, chartData[0])?.juniorTokenPrice ?? 1 + return [min, max] }, [chartData]) @@ -165,8 +191,6 @@ function PoolPerformanceChart() { return result } - console.log(rangeFilters) - return ( @@ -191,31 +215,8 @@ function PoolPerformanceChart() { Download - - - - {chartData.length > 0 && - rangeFilters.map((rangeFilter, index) => ( - - setRange(rangeFilter)}> - - {rangeFilter.label} - - - - {index !== rangeFilters.length - 1 && ( - - )} - - ))} - - - - + + {chartData?.length ? ( @@ -238,7 +239,7 @@ function PoolPerformanceChart() { formatBalanceAbbreviated(tick, '', 0)} yAxisId="left" width={80} @@ -246,11 +247,12 @@ function PoolPerformanceChart() { formatBalanceAbbreviated(tick, '', 6)} yAxisId="right" orientation="right" domain={priceRange} + hide={true} /> {formatDate(payload[0].payload.day)} - {payload.map(({ name, value }, index) => ( - - - {name === 'nav' ? 'NAV' : name === 'price' ? 'Token price' : 'Cash'} - - - {name === 'nav' && typeof value === 'number' - ? formatBalance(value, 'USD') - : typeof value === 'number' - ? formatBalance(value, 'USD', 6) - : '-'} - - - ))} + {payload.map(({ name, value }, index) => { + let label = '' + if (name === 'nav') label = 'NAV' + else if (name === 'juniorTokenPrice') label = 'Junior Token Price' + else if (name === 'seniorTokenPrice') label = 'Senior Token Price' + else label = 'Cash' + + return ( + + + {label} + + + {typeof value === 'number' + ? formatBalance( + value, + name === 'nav' ? pool.currency.symbol ?? 'USD' : '', + name === 'juniorTokenPrice' || name === 'seniorTokenPrice' ? 6 : 0 + ) + : '-'} + + + ) + })} ) } return null }} /> - - + + + {chartData.some((d) => d.seniorTokenPrice !== null) && ( + + )} ) : ( @@ -293,7 +331,7 @@ function PoolPerformanceChart() { function CustomLegend({ data, - selectedTabIndex, + setRange, }: { data: { currency: string @@ -301,10 +339,8 @@ function CustomLegend({ juniorTokenPrice: number seniorTokenPrice?: number | null } - selectedTabIndex: number + setRange: (value: { value: string; label: string }) => void }) { - const theme = useTheme() - const Dot = ({ color }: { color: string }) => ( ) @@ -315,32 +351,35 @@ function CustomLegend({ label: `NAV ${data.currency}`, value: formatBalance(data.nav), type: 'nav', + show: true, }, { color: 'textGold', label: 'Junior token price', value: data.juniorTokenPrice ?? 0, type: 'singleTrancheTokenPrice', + show: true, }, { color: 'textPrimary', label: 'Senior token price', value: data.seniorTokenPrice ?? 0, type: 'singleTrancheTokenPrice', + show: !!data.seniorTokenPrice, }, ] - const accordionData = [ - { label: '30 days', value: '30d' }, - { label: '90 days', value: '90d' }, - { label: 'Year to date', value: 'ytd' }, - { label: 'All', value: 'all' }, - ] + const toggleRange = (e: any) => { + const value = e.target.value + const range = rangeFilters.find((range) => range.value === value) + setRange(range ?? rangeFilters[0]) + } return ( - + {buildData.map((item, index) => { + if (!item.show) return return ( @@ -353,7 +392,7 @@ function CustomLegend({ })} - ) @@ -364,7 +403,7 @@ const CustomTick = ({ x, y, payload }: any) => { return ( {children} @@ -44,7 +44,7 @@ export function TooltipContainer({ children }: { children: React.ReactNode }) { export function TooltipTitle({ children }: { children: React.ReactNode }) { return ( - + {children} ) diff --git a/centrifuge-app/src/pages/Pool/Overview/index.tsx b/centrifuge-app/src/pages/Pool/Overview/index.tsx index ed871db0e..e5446fb4f 100644 --- a/centrifuge-app/src/pages/Pool/Overview/index.tsx +++ b/centrifuge-app/src/pages/Pool/Overview/index.tsx @@ -115,8 +115,8 @@ export function PoolDetailOverview() { .reverse() return ( - - + + }> diff --git a/fabric/src/components/Select/index.tsx b/fabric/src/components/Select/index.tsx index 38fa43e53..e6e1eb8af 100644 --- a/fabric/src/components/Select/index.tsx +++ b/fabric/src/components/Select/index.tsx @@ -17,6 +17,7 @@ export type SelectProps = React.SelectHTMLAttributes & { placeholder?: string errorMessage?: string small?: boolean + hideBorder?: boolean } const StyledSelect = styled.select` @@ -31,14 +32,11 @@ const StyledSelect = styled.select` cursor: pointer; line-height: inherit; text-overflow: ellipsis; + font-weight: 500; &:disabled { cursor: default; } - - &:focus { - color: ${({ theme }) => theme.colors.textSelected}; - } ` export function SelectInner({ @@ -79,7 +77,7 @@ export function SelectInner({ ) } -export function Select({ label, errorMessage, id, ...rest }: SelectProps) { +export function Select({ label, errorMessage, id, hideBorder, ...rest }: SelectProps) { const defaultId = React.useId() id ??= defaultId return ( @@ -89,7 +87,7 @@ export function Select({ label, errorMessage, id, ...rest }: SelectProps) { disabled={rest.disabled} errorMessage={errorMessage} inputElement={ - + } diff --git a/fabric/src/components/TextInput/index.tsx b/fabric/src/components/TextInput/index.tsx index e48312d35..5f5775b17 100644 --- a/fabric/src/components/TextInput/index.tsx +++ b/fabric/src/components/TextInput/index.tsx @@ -49,12 +49,11 @@ export const StyledTextInput = styled.input` margin: 0; } ` - -export const StyledInputBox = styled(Shelf)` +export const StyledInputBox = styled(Shelf)<{ hideBorder?: boolean }>` width: 100%; position: relative; background: ${({ theme }) => theme.colors.backgroundPage}; - border: 1px solid ${({ theme }) => theme.colors.borderPrimary}; + border: ${({ hideBorder, theme }) => (hideBorder ? 'none' : `1px solid ${theme.colors.borderPrimary}`)}; border-radius: ${({ theme }) => theme.radii.input}px; &::before {