Skip to content

Commit

Permalink
Add new fields to the assets table
Browse files Browse the repository at this point in the history
  • Loading branch information
kattylucy committed Oct 23, 2024
1 parent ec01493 commit 3d38426
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 136 deletions.
31 changes: 12 additions & 19 deletions centrifuge-app/src/components/AssetSummary.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Loan, TinlakeLoan } from '@centrifuge/centrifuge-js'
import { Box, Shelf, Stack, Text } from '@centrifuge/fabric'
import { Shelf, Stack, Text } from '@centrifuge/fabric'
import * as React from 'react'
import { useTheme } from 'styled-components'
import { LoanLabel } from './LoanLabel'

type Props = {
data?: {
Expand All @@ -16,26 +15,20 @@ type Props = {
export function AssetSummary({ data, children, loan }: Props) {
const theme = useTheme()
return (
<Stack bg={theme.colors.backgroundSecondary} pl={3}>
<Box paddingTop={3}>
<Shelf gap="2">
<Text variant="heading2">Details</Text>
{loan && <LoanLabel loan={loan} />}
</Shelf>
</Box>
<Shelf
gap="6"
py="3"
style={{
boxShadow: `0 1px 0 ${theme.colors.borderPrimary}`,
}}
>
<Stack
bg={theme.colors.backgroundSecondary}
border={`1px solid ${theme.colors.borderSecondary}`}
borderRadius={10}
padding={2}
margin={3}
>
<Shelf gap="6">
{data?.map(({ label, value }, index) => (
<Stack gap="4px" key={`${value}-${label}-${index}`}>
<Text variant="body3" style={{ fontWeight: 500 }}>
<Stack key={`${value}-${label}-${index}`}>
<Text variant="body2" styke={{ margin: 0, padding: 0 }}>
{label}
</Text>
<Text variant="body2">{value}</Text>
<Text variant="heading">{value}</Text>
</Stack>
))}
{children}
Expand Down
1 change: 0 additions & 1 deletion centrifuge-app/src/components/LayoutBase/styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,6 @@ export const ContentWrapper = styled.div`
@media (min-width: ${({ theme }) => theme.breakpoints['L']}) {
margin-left: 15vw;
width: calc(100% - 15vw);
margin-top: 10px;
}
`
211 changes: 130 additions & 81 deletions centrifuge-app/src/components/LoanList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useBasePath } from '@centrifuge/centrifuge-app/src/utils/useBasePath'
import { Loan, TinlakeLoan } from '@centrifuge/centrifuge-js'
import { CurrencyBalance, Loan, TinlakeLoan } from '@centrifuge/centrifuge-js'
import {
Box,
Pagination,
Expand All @@ -13,15 +13,13 @@ import {
import get from 'lodash/get'
import * as React from 'react'
import { useParams } from 'react-router'
import { formatNftAttribute } from '../pages/Loan/utils'
import { nftMetadataSchema } from '../schemas'
import { LoanTemplate, LoanTemplateAttribute } from '../types'
import { formatDate } from '../utils/date'
import { formatBalance } from '../utils/formatting'
import { formatBalance, formatPercentage } from '../utils/formatting'
import { useFilters } from '../utils/useFilters'
import { useMetadata } from '../utils/useMetadata'
import { useCentNFT } from '../utils/useNFTs'
import { usePool, usePoolMetadata } from '../utils/usePools'
import { useAllPoolAssetSnapshots, usePool } from '../utils/usePools'
import { Column, DataTable, SortableTableHeader } from './DataTable'
import { LoadBoundary } from './LoadBoundary'
import { prefetchRoute } from './Root'
Expand All @@ -31,6 +29,11 @@ type Row = (Loan | TinlakeLoan) & {
originationDateSortKey: string
status: 'Created' | 'Active' | 'Closed' | ''
maturityDate: string | null
marketPrice: CurrencyBalance
marketValue: CurrencyBalance
unrealizedPL: CurrencyBalance
realizedPL: CurrencyBalance
portfolioPercentage: string
}

type Props = {
Expand All @@ -44,55 +47,69 @@ export function LoanList({ loans }: Props) {
const pool = usePool(poolId)
const isTinlakePool = poolId?.startsWith('0x')
const basePath = useBasePath()
const snapshots = useAllPoolAssetSnapshots(pool.id, new Date().toString())
const loansData = isTinlakePool
? loans
: (loans ?? []).filter((loan) => 'valuationMethod' in loan.pricing && loan.pricing.valuationMethod !== 'cash')

const snapshotsValues =
snapshots?.reduce((acc: { [key: string]: any }, snapshot) => {
const id = snapshot.assetId.split('-')[1]
acc[id] = {
marketPrice: snapshot.currentPrice,
marketValue: snapshot.presentValue,
unrealizedPL: snapshot.unrealizedProfitAtMarketPrice,
realizedPL: snapshot.sumRealizedProfitFifo,
}
return acc
}, {}) ?? {}

const totalMarketValue = Object.values(snapshotsValues).reduce((sum, snapshot) => {
return sum + (snapshot.marketValue?.toDecimal().toNumber() ?? 0)
}, 0)

const { data: poolMetadata } = usePoolMetadata(pool)
const templateIds = poolMetadata?.loanTemplates?.map((s) => s.id) ?? []
const templateId = templateIds.at(-1)
const { data: templateMetadata } = useMetadata<LoanTemplate>(templateId)
const loansWithLabelStatus = React.useMemo(() => {
return loans.sort((a, b) => {
return loansData.sort((a, b) => {
const aId = get(a, 'id') as string
const bId = get(b, 'id') as string

return aId.localeCompare(bId)
})
}, [isTinlakePool, loans])
}, [isTinlakePool, loansData])

Check warning on line 78 in centrifuge-app/src/components/LoanList.tsx

View workflow job for this annotation

GitHub Actions / ff-prod / build-app

React Hook React.useMemo has an unnecessary dependency: 'isTinlakePool'. Either exclude it or remove the dependency array

Check warning on line 78 in centrifuge-app/src/components/LoanList.tsx

View workflow job for this annotation

GitHub Actions / build-app

React Hook React.useMemo has an unnecessary dependency: 'isTinlakePool'. Either exclude it or remove the dependency array

const filters = useFilters({
data: loansWithLabelStatus,
data: loansWithLabelStatus as Loan[],
})

React.useEffect(() => {
prefetchRoute('/pools/1/assets/1')
}, [])

const additionalColumns: Column[] =
templateMetadata?.keyAttributes
?.filter((key) => key !== 'term')
.map((key) => {
const attr = templateMetadata.attributes![key]
return {
align: 'left',
header: attr.label,
cell: (l: Row) => <AssetMetadataField name={key} attribute={attr} loan={l} />,
}
}) || []
const rows: Row[] = filters.data.map((loan) => {
const snapshot = snapshotsValues?.[loan.id]
const marketValue = snapshot?.marketValue?.toDecimal().toNumber() ?? 0

const rows: Row[] = filters.data.map((loan) => ({
nftIdSortKey: loan.asset.nftId,
idSortKey: parseInt(loan.id, 10),
outstandingDebtSortKey: loan.status !== 'Closed' && loan?.outstandingDebt?.toDecimal().toNumber(),
originationDateSortKey:
loan.status === 'Active' &&
loan?.originationDate &&
'interestRate' in loan.pricing &&
!loan?.pricing.interestRate?.isZero() &&
!loan?.totalBorrowed?.isZero()
? loan.originationDate
: '',
maturityDate: loan.pricing.maturityDate,
...loan,
}))
const portfolioPercentage =
loan.status === 'Closed' || totalMarketValue === 0 ? 0 : (marketValue / totalMarketValue) * 100

return {
nftIdSortKey: loan.asset.nftId,
idSortKey: parseInt(loan.id, 10),
outstandingDebtSortKey: loan.status !== 'Closed' && loan?.outstandingDebt?.toDecimal().toNumber(),
originationDateSortKey:
loan.status === 'Active' &&
loan?.originationDate &&
'interestRate' in loan.pricing &&
!loan?.pricing.interestRate?.isZero() &&
!loan?.totalBorrowed?.isZero()
? loan.originationDate
: '',
maturityDate: loan.pricing.maturityDate,
portfolioPercentage,
...snapshot,
...loan,
}
})

const hasMaturityDate = rows.some((loan) => loan.maturityDate)

Expand All @@ -102,25 +119,21 @@ export function LoanList({ loans }: Props) {
header: <SortableTableHeader label={isTinlakePool ? 'NFT ID' : 'Asset'} />,
cell: (l: Row) => <AssetName loan={l} />,
sortKey: 'idSortKey',
width: 'minmax(300px, 1fr)',
},
...(additionalColumns?.length
? additionalColumns
: [
{
align: 'left',
header: <SortableTableHeader label="Financing date" />,
cell: (l: Row) => {
if (l.poolId.startsWith('0x') && l.id !== '0') {
return formatDate((l as TinlakeLoan).originationDate)
}
return l.status === 'Active' && 'valuationMethod' in l.pricing && l.pricing.valuationMethod !== 'cash'
? formatDate(l.originationDate)
: '-'
},
sortKey: 'originationDateSortKey',
},
]),

{
align: 'left',
header: <SortableTableHeader label="Financing date" />,
cell: (l: Row) => {
if (l.poolId.startsWith('0x') && l.id !== '0') {
return formatDate((l as TinlakeLoan).originationDate)
}
return l.status === 'Active' && 'valuationMethod' in l.pricing && l.pricing.valuationMethod !== 'cash'
? formatDate(l.originationDate)
: '-'
},
sortKey: 'originationDateSortKey',
},
...(hasMaturityDate
? [
{
Expand All @@ -138,12 +151,67 @@ export function LoanList({ loans }: Props) {
},
]
: []),
{
align: 'left',
header: <SortableTableHeader label="Amount" />,
cell: (l: Row) => <Amount loan={l} />,
sortKey: 'outstandingDebtSortKey',
},
...(isTinlakePool
? []
: [
{
align: 'left',
header: <SortableTableHeader label="Amount" />,
cell: (l: Row) => <Amount loan={l} />,
sortKey: 'outstandingDebtSortKey',
},
]),
...(isTinlakePool
? []
: [
{
align: 'left',
header: <SortableTableHeader label="Market price" />,
cell: (l: Row) => formatBalance(l.marketPrice ?? '', pool.currency, 2, 0),
sortKey: 'marketPriceSortKey',
},
]),
...(isTinlakePool
? []
: [
{
align: 'left',
header: <SortableTableHeader label="Market value" />,
cell: (l: Row) => formatBalance(l.marketValue ?? 0, pool.currency, 2, 0),
sortKey: 'marketValueSortKey',
},
]),
...(isTinlakePool
? []
: [
{
align: 'left',
header: <SortableTableHeader label="Unrealized P&L" />,
cell: (l: Row) => formatBalance(l.unrealizedPL ?? '', pool.currency, 2, 0),
sortKey: 'unrealizedPLSortKey',
},
]),
...(isTinlakePool
? []
: [
{
align: 'left',
header: <SortableTableHeader label="Realized P&L" />,
cell: (l: Row) => formatBalance(l.realizedPL ?? '', pool.currency, 2, 0),
sortKey: 'realizedPLSortKey',
},
]),
...(isTinlakePool
? []
: [
{
align: 'left',
header: <SortableTableHeader label="Portfolio" />,
cell: (l: Row) => formatPercentage(l.portfolioPercentage ?? 0, true, undefined, 1),
sortKey: 'portfolioSortKey',
width: '80px',
},
]),
].filter(Boolean) as Column[]

const pagination = usePagination({ data: rows, pageSize: 20 })
Expand Down Expand Up @@ -173,25 +241,6 @@ export function LoanList({ loans }: Props) {
)
}

function AssetMetadataField({ loan, name, attribute }: { loan: Row; name: string; attribute: LoanTemplateAttribute }) {
const isTinlakePool = loan.poolId.startsWith('0x')
const nft = useCentNFT(loan.asset.collectionId, loan.asset.nftId, false, isTinlakePool)
const { data: metadata, isLoading } = useMetadata(nft?.metadataUri, nftMetadataSchema)

return (
<Shelf gap="1" style={{ whiteSpace: 'nowrap', maxWidth: '100%' }}>
<TextWithPlaceholder
isLoading={isLoading}
width={12}
variant="body2"
style={{ overflow: 'hidden', maxWidth: '300px', textOverflow: 'ellipsis' }}
>
{metadata?.properties?.[name] ? formatNftAttribute(metadata?.properties?.[name], attribute) : '-'}
</TextWithPlaceholder>
</Shelf>
)
}

export function AssetName({ loan }: { loan: Pick<Row, 'id' | 'poolId' | 'asset' | 'pricing'> }) {
const isTinlakePool = loan.poolId.startsWith('0x')
const nft = useCentNFT(loan.asset.collectionId, loan.asset.nftId, false, isTinlakePool)
Expand Down
12 changes: 1 addition & 11 deletions centrifuge-app/src/components/PageSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,7 @@ export function PageSection({
}: Props) {
const [open, setOpen] = React.useState(defaultOpen)
return (
<Stack
as="section"
pl={3}
pr={[3, 3, 7]}
pt={3}
pb={4}
gap={3}
borderTopWidth={1}
borderTopStyle="solid"
borderTopColor="borderPrimary"
>
<Stack as="section" pl={3} pr={[3, 3, 3]} pt={3} pb={4} gap={3}>
{(title || titleAddition) && (
<Shelf justifyContent="space-between" as="header">
<Stack>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ export const TransactionHistoryTable = ({
</Text>
),
sortKey: 'transactionDate',
width: '200px',
},
{
align: 'left',
Expand All @@ -249,19 +250,21 @@ export const TransactionHistoryTable = ({
)
},
sortKey: 'transaction',
width: '60%',
},
{
align: 'right',
align: 'left',
header: <SortableTableHeader label="Amount" />,
cell: ({ amount, netFlow }: Row) => (
<Text as="span" variant="body3">
{amount ? `${activeAssetId && netFlow === 'negative' ? '-' : ''}${formatBalance(amount, 'USD', 2, 2)}` : ''}
</Text>
),
sortKey: 'amount',
width: '250px',
},
{
align: 'right',
align: 'center',
header: 'View transaction',
cell: ({ hash }: Row) => {
return (
Expand All @@ -276,6 +279,7 @@ export const TransactionHistoryTable = ({
</Stack>
)
},
width: '110px',
},
]

Expand Down
Loading

0 comments on commit 3d38426

Please sign in to comment.