Skip to content

Commit

Permalink
Asset page updates
Browse files Browse the repository at this point in the history
  • Loading branch information
kattylucy committed Oct 24, 2024
1 parent f726502 commit e182f0f
Show file tree
Hide file tree
Showing 9 changed files with 70 additions and 108 deletions.
105 changes: 45 additions & 60 deletions centrifuge-app/src/components/Charts/AssetPerformanceChart.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Pool } from '@centrifuge/centrifuge-js'
import { Box, Card, Shelf, Spinner, Stack, Text } from '@centrifuge/fabric'
import { AnchorButton, Box, Card, IconDownload, Shelf, Spinner, Stack, Tabs, TabsItem, Text } from '@centrifuge/fabric'
import * as React from 'react'
import { CartesianGrid, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts'
import styled, { useTheme } from 'styled-components'
import { useTheme } from 'styled-components'
import { formatDate } from '../../utils/date'
import { formatBalance, formatBalanceAbbreviated } from '../../utils/formatting'
import { TinlakePool } from '../../utils/tinlake/useTinlakePools'
Expand All @@ -24,30 +24,13 @@ interface Props {
loanId: string
}

const FilterButton = styled(Stack)`
&:hover {
cursor: pointer;
}
`

const filterOptions = [
{ value: 'price', label: 'Price' },
{ value: 'value', label: 'Asset value' },
] as const

function AssetPerformanceChart({ pool, poolId, loanId }: Props) {
const theme = useTheme()
const chartColor = theme.colors.accentPrimary
const asset = useLoan(poolId, loanId)
const assetSnapshots = useAssetSnapshots(poolId, loanId)

const [activeFilter, setActiveFilter] = React.useState<(typeof filterOptions)[number]>(filterOptions[0])

React.useEffect(() => {
if (assetSnapshots && assetSnapshots[0]?.currentPrice?.toString() === '0') {
setActiveFilter(filterOptions[1])
}
}, [assetSnapshots])
const [selectedTabIndex, setSelectedTabIndex] = React.useState(0)

const data: ChartData[] = React.useMemo(() => {
if (!asset || !assetSnapshots) return []
Expand Down Expand Up @@ -139,17 +122,44 @@ function AssetPerformanceChart({ pool, poolId, loanId }: Props) {
if (!assetSnapshots) return <Spinner style={{ margin: 'auto', height: 350 }} />

return (
<Card p={3} height={320} borderColor={theme.colors.borderSecondary}>
<Card p={3} height={320} variant="secondary">
<Stack gap={2}>
<Box display="flex">
<Text variant="heading4">
{asset && 'valuationMethod' in asset.pricing && asset?.pricing.valuationMethod !== 'cash'
? 'Asset performance'
: 'Cash balance'}
</Text>
<Text variant="body2" style={{ marginLeft: 4 }}>
({pool.currency.name ?? 'USD'})
</Text>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={3}>
<Box display="flex">
<Text variant="heading4">
{asset && 'valuationMethod' in asset.pricing && asset?.pricing.valuationMethod !== 'cash'
? 'Asset performance'
: 'Cash balance'}
</Text>
<Text variant="body2" style={{ marginLeft: 4 }}>
({pool.currency.name ?? 'USD'})
</Text>
</Box>
{!(assetSnapshots && assetSnapshots[0]?.currentPrice?.toString() === '0') && (
<Stack>
<Shelf justifyContent="flex-end">
{data.length > 0 && (
<Tabs selectedIndex={selectedTabIndex} onChange={(index) => setSelectedTabIndex(index)}>
<TabsItem styleOverrides={{ padding: '8px' }} showBorder variant="secondary">
Price
</TabsItem>
<TabsItem styleOverrides={{ padding: '8px' }} showBorder variant="secondary">
Asset value
</TabsItem>
</Tabs>
)}
</Shelf>
</Stack>
)}
<AnchorButton
download={`pool-${poolId}-timeseries.csv`}
// href={dataUrl}
variant="inverted"
icon={IconDownload}
small
>
Download
</AnchorButton>
</Box>

{isChartEmpty && (
Expand All @@ -158,31 +168,6 @@ function AssetPerformanceChart({ pool, poolId, loanId }: Props) {
</Text>
)}

{!(assetSnapshots && assetSnapshots[0]?.currentPrice?.toString() === '0') && (
<Stack>
<Shelf justifyContent="flex-end">
{data.length > 0 &&
filterOptions.map((filter, index) => (
<React.Fragment key={filter.label}>
<FilterButton gap={1} onClick={() => setActiveFilter(filter)}>
<Text variant="body3" whiteSpace="nowrap">
<Text variant={filter.value === activeFilter.value && 'emphasized'}>{filter.label}</Text>
</Text>
<Box
width="100%"
backgroundColor={filter.value === activeFilter.value ? '#000000' : '#E0E0E0'}
height="2px"
/>
</FilterButton>
{index !== filterOptions.length - 1 && (
<Box width="24px" backgroundColor="#E0E0E0" height="2px" alignSelf="flex-end" />
)}
</React.Fragment>
))}
</Shelf>
</Stack>
)}

<Shelf gap={4} width="100%">
{data?.length ? (
<ResponsiveContainer width="100%" height={200} minHeight={200} maxHeight={200}>
Expand Down Expand Up @@ -211,7 +196,7 @@ function AssetPerformanceChart({ pool, poolId, loanId }: Props) {
tickLine={false}
style={{ fontSize: '10px', fill: theme.colors.textSecondary }}
tickFormatter={(tick: number) => formatBalanceAbbreviated(tick, '', 2)}
domain={activeFilter.value === 'price' ? priceRange : [0, 'auto']}
domain={selectedTabIndex === 0 ? priceRange : [0, 'auto']}
width={90}
/>
<CartesianGrid stroke={theme.colors.borderPrimary} vertical={false} />
Expand Down Expand Up @@ -252,7 +237,7 @@ function AssetPerformanceChart({ pool, poolId, loanId }: Props) {
}}
/>

{activeFilter.value === 'price' && (
{selectedTabIndex === 0 && (
<Line
type="monotone"
dataKey="historicPrice"
Expand All @@ -261,7 +246,7 @@ function AssetPerformanceChart({ pool, poolId, loanId }: Props) {
dot={false}
/>
)}
{activeFilter.value === 'price' && (
{selectedTabIndex === 0 && (
<Line
type="monotone"
dataKey="futurePrice"
Expand All @@ -272,7 +257,7 @@ function AssetPerformanceChart({ pool, poolId, loanId }: Props) {
/>
)}

{activeFilter.value === 'value' && (
{selectedTabIndex === 1 && (
<Line
type="monotone"
dataKey="historicPV"
Expand All @@ -281,7 +266,7 @@ function AssetPerformanceChart({ pool, poolId, loanId }: Props) {
dot={false}
/>
)}
{activeFilter.value === 'value' && (
{selectedTabIndex === 1 && (
<Line
type="monotone"
dataKey="futurePV"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,9 +286,7 @@ export const TransactionHistoryTable = ({
return (
<Stack gap={2}>
<Shelf justifyContent="space-between">
<Text fontSize="18px" fontWeight="500">
Transaction history
</Text>
<Text variant="heading4">Transaction history</Text>
<Shelf>
{transactions?.length! > 8 && preview && (
<AnchorButton href={`#/pools/${poolId}/transactions`} small variant="inverted">
Expand Down
6 changes: 2 additions & 4 deletions centrifuge-app/src/pages/Loan/HoldingsValues.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,9 @@ export function HoldingsValues({ pool, transactions, currentFace, pricing }: Pro
]

return (
<Card p={3}>
<Card p={3} variant="secondary">
<Stack gap={2}>
<Text fontSize="18px" fontWeight="500">
Holdings
</Text>
<Text variant="heading4">Holdings</Text>
<MetricsTable metrics={metrics} />
</Stack>
</Card>
Expand Down
6 changes: 2 additions & 4 deletions centrifuge-app/src/pages/Loan/KeyMetrics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,9 @@ export function KeyMetrics({ pool, loan }: Props) {
]

return (
<Card p={3}>
<Card p={3} variant="secondary">
<Stack gap={2}>
<Text fontSize="18px" fontWeight="500">
Key metrics
</Text>
<Text variant="heading4">Key metrics</Text>
<MetricsTable metrics={metrics} />
</Stack>
</Card>
Expand Down
12 changes: 4 additions & 8 deletions centrifuge-app/src/pages/Loan/MetricsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type Props = {

export function MetricsTable({ metrics }: Props) {
return (
<Box borderStyle="solid" borderWidth="1px" borderColor="borderPrimary">
<Box>
{metrics.map(({ label, value }, index) => {
const multirow = value && value.length > 20
const asLink = value && /^(https?:\/\/[^\s]+)$/.test(value)
Expand All @@ -33,25 +33,21 @@ export function MetricsTable({ metrics }: Props) {
}
: {}

const combinedStyle: React.CSSProperties = { ...defaultStyle, ...multiRowStyle }
const combinedStyle: React.CSSProperties = { ...defaultStyle, ...multiRowStyle, textAlign: 'right' }

return (
<Grid
borderBottomStyle={index === metrics.length - 1 ? 'none' : 'solid'}
borderBottomWidth={index === metrics.length - 1 ? '0' : '1px'}
borderBottomColor={index === metrics.length - 1 ? 'none' : 'borderPrimary'}
key={index}
px={1}
gridTemplateColumns="1fr 1fr"
width="100%"
alignItems="center"
gap={2}
height={multirow ? 'auto' : 32}
>
<Text variant="body3" textOverflow="ellipsis" whiteSpace="nowrap">
<Text color="textSecondary" variant="body2" textOverflow="ellipsis" whiteSpace="nowrap">
{label}
</Text>
<Text variant="body3" style={combinedStyle}>
<Text variant="heading4" style={combinedStyle}>
{asLink ? <AnchorTextLink href={value}>{value}</AnchorTextLink> : value}
</Text>
</Grid>
Expand Down
12 changes: 4 additions & 8 deletions centrifuge-app/src/pages/Loan/PricingValues.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,9 @@ export function PricingValues({ loan, pool }: Props) {
const accruedPrice = 'currentPrice' in loan && loan.currentPrice

return (
<Card p={3}>
<Card p={3} variant="secondary">
<Stack gap={2}>
<Text fontSize="18px" fontWeight="500">
Pricing
</Text>
<Text variant="heading4">Pricing</Text>
<MetricsTable
metrics={[
...('isin' in pricing.priceId ? [{ label: 'ISIN', value: pricing.priceId.isin }] : []),
Expand All @@ -76,7 +74,7 @@ export function PricingValues({ loan, pool }: Props) {
: '-',
},
{
label: <Tooltips type="linearAccrual" />,
label: <Tooltips type="linearAccrual" size="med" />,
value: pricing.withLinearPricing ? 'Enabled' : 'Disabled',
},
...(loan.status === 'Active' && loan.outstandingDebt.toDecimal().lte(0)
Expand All @@ -102,9 +100,7 @@ export function PricingValues({ loan, pool }: Props) {
return (
<Card p={3}>
<Stack gap={2}>
<Text fontSize="18px" fontWeight="500">
Pricing
</Text>
<Text variant="heading4">Pricing</Text>
<MetricsTable
metrics={[
...('valuationMethod' in pricing && pricing.valuationMethod !== 'cash'
Expand Down
22 changes: 7 additions & 15 deletions centrifuge-app/src/pages/Loan/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ function Loan() {
const metadataIsLoading = poolMetadataIsLoading || nftMetadataIsLoading
const borrowerAssetTransactions = useBorrowerAssetTransactions(`${poolId}`, `${loanId}`)
const isOracle = loan && 'valuationMethod' in loan.pricing && loan.pricing.valuationMethod === 'oracle'
const isCash = loan && 'valuationMethod' in loan.pricing && loan.pricing.valuationMethod !== 'cash'

const currentFace =
loan?.pricing && 'outstandingQuantity' in loan.pricing
Expand Down Expand Up @@ -215,7 +216,7 @@ function Loan() {
</React.Suspense>
)}

{'valuationMethod' in loan.pricing && loan.pricing.valuationMethod !== 'cash' && (
{isCash && (
<React.Suspense fallback={<Spinner />}>
<PricingValues loan={loan} pool={pool} />
</React.Suspense>
Expand All @@ -226,11 +227,9 @@ function Loan() {
if (!isPublic) return null
return (
<React.Suspense fallback={<Spinner />}>
<Card p={3}>
<Card p={3} variant="secondary">
<Stack gap={2}>
<Text fontSize="18px" fontWeight="500">
{section.name}
</Text>
<Text variant="heading4">{section.name}</Text>
<MetricsTable
metrics={section.attributes
.filter(
Expand All @@ -257,7 +256,7 @@ function Loan() {
</Grid>

{borrowerAssetTransactions?.length ? (
'valuationMethod' in loan.pricing && loan.pricing.valuationMethod === 'cash' ? (
isCash ? (
<TransactionHistoryTable
transactions={borrowerAssetTransactions ?? []}
poolId={poolId}
Expand All @@ -267,18 +266,11 @@ function Loan() {
) : (
<Grid height="fit-content" gridTemplateColumns={['1fr']} gap={[2, 2, 3]}>
<Stack gap={2}>
<Text fontSize="18px" fontWeight="500">
Transaction history
</Text>

<Text variant="heading4">Transaction history</Text>
<TransactionTable
transactions={borrowerAssetTransactions}
currency={pool.currency.symbol}
loanType={
'valuationMethod' in loan.pricing && loan.pricing.valuationMethod === 'oracle'
? 'external'
: 'internal'
}
loanType={isOracle ? 'external' : 'internal'}
poolType={poolMetadata?.pool?.asset.class}
decimals={pool.currency.decimals}
pricing={loan.pricing as PricingInfo}
Expand Down
2 changes: 1 addition & 1 deletion centrifuge-app/src/pages/Pool/Assets/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export function PoolDetailAssets() {
),
heading: false,
},
...(!isTinlakePool
...(!isTinlakePool && cashLoans.length
? [
{
label: <Tooltips type="offchainCash" />,
Expand Down
9 changes: 4 additions & 5 deletions fabric/src/components/Card/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,20 @@ import styled from 'styled-components'
import { Box, BoxProps } from '../Box'

type Props = {
variant?: 'default' | 'interactive' | 'overlay'
variant?: 'default' | 'interactive' | 'overlay' | 'secondary'
backgroundColor?: string
borderColor?: string
}

export type CardProps = Props &
Omit<BoxProps, 'border' | 'borderColor' | 'borderWidth' | 'borderStyle' | 'bg' | 'backgroundColor'>

export const Card = styled(Box)<Props>(({ variant = 'default', backgroundColor, borderColor }) =>
export const Card = styled(Box)<Props>(({ variant = 'default', backgroundColor }) =>
css({
bg: backgroundColor ?? 'white',
borderRadius: 'card',
borderWidth: variant === 'default' && !backgroundColor ? 1 : 0,
borderWidth: variant === 'default' || (variant === 'secondary' && !backgroundColor) ? 1 : 0,
borderStyle: 'solid',
borderColor: borderColor ?? 'borderPrimary',
borderColor: variant === 'secondary' ? 'borderSecondary' : 'borderPrimary',
boxShadow: variant === 'interactive' ? 'cardInteractive' : variant === 'overlay' ? 'cardOverlay' : undefined,
transition: 'box-shadow 100ms ease',

Expand Down

0 comments on commit e182f0f

Please sign in to comment.