diff --git a/centrifuge-app/src/components/Report/AssetList.tsx b/centrifuge-app/src/components/Report/AssetList.tsx
index 003954369..39cf6fdb0 100644
--- a/centrifuge-app/src/components/Report/AssetList.tsx
+++ b/centrifuge-app/src/components/Report/AssetList.tsx
@@ -1,10 +1,10 @@
-import { Loan, Pool } from '@centrifuge/centrifuge-js'
+import { Pool } from '@centrifuge/centrifuge-js'
import { Text } from '@centrifuge/fabric'
-import * as React from 'react'
+import { useContext, useEffect, useMemo } from 'react'
import { formatDate } from '../../utils/date'
-import { formatBalance, formatPercentage } from '../../utils/formatting'
+import { formatBalance } from '../../utils/formatting'
import { getCSVDownloadUrl } from '../../utils/getCSVDownloadUrl'
-import { useLoans } from '../../utils/useLoans'
+import { useAllPoolAssetSnapshots, usePoolMetadata } from '../../utils/usePools'
import { DataTable, SortableTableHeader } from '../DataTable'
import { Spinner } from '../Spinner'
import { ReportContext } from './ReportContext'
@@ -16,155 +16,214 @@ const valuationLabels = {
discountedCashFlow: 'Non-fungible asset - DCF',
outstandingDebt: 'Non-fungible asset - at par',
oracle: 'Fungible asset - external pricing',
- cash: 'Cash',
+}
+
+function getColumnConfig(poolCreditType: string, symbol: string) {
+ if (poolCreditType === 'privateCredit') {
+ return [
+ {
+ header: 'ID',
+ align: 'left',
+ csvOnly: false,
+ formatter: noop,
+ },
+ { header: 'Name', align: 'left', csvOnly: false, formatter: noop },
+ {
+ header: 'Value',
+ align: 'right',
+ csvOnly: false,
+ sortable: true,
+ formatter: (v: any) => formatBalance(v, symbol, 2),
+ },
+ {
+ header: 'Principal outstanding',
+ align: 'right',
+ csvOnly: false,
+ sortable: true,
+ formatter: (v: any) => formatBalance(v, symbol, 2),
+ },
+ {
+ header: 'Interest outstanding',
+ align: 'right',
+ csvOnly: false,
+ sortable: true,
+ formatter: (v: any) => formatBalance(v, symbol, 2),
+ },
+ {
+ header: 'Principal repaid',
+ align: 'right',
+ csvOnly: false,
+ sortable: true,
+ formatter: (v: any) => formatBalance(v, symbol, 2),
+ },
+ {
+ header: 'Interest repaid',
+ align: 'right',
+ csvOnly: false,
+ sortable: true,
+ formatter: (v: any) => formatBalance(v, symbol, 2),
+ },
+ {
+ header: 'Additional repaid',
+ align: 'right',
+ csvOnly: false,
+ sortable: true,
+ formatter: (v: any) => formatBalance(v, symbol, 2),
+ },
+ { header: 'Origination date', align: 'left', csvOnly: false, sortable: true, formatter: formatDate },
+ { header: 'Maturity date', align: 'left', csvOnly: false, sortable: true, formatter: formatDate },
+ { header: 'Valuation method', align: 'left', csvOnly: false, formatter: (v: any) => formatBalance(v, symbol, 2) },
+ { header: 'Advance rate', align: 'left', csvOnly: false, formatter: (v: any) => formatBalance(v, symbol, 2) },
+ { header: 'Collateral value', align: 'left', csvOnly: false, formatter: (v: any) => formatBalance(v, symbol, 2) },
+ {
+ header: 'Probability of default (PD)',
+ align: 'left',
+ csvOnly: false,
+ formatter: (v: any) => formatBalance(v, symbol, 2),
+ },
+ {
+ header: 'Loss given default (LGD)',
+ align: 'left',
+ csvOnly: false,
+ formatter: (v: any) => formatBalance(v, symbol, 2),
+ },
+ { header: 'Discount rate', align: 'left', csvOnly: false, formatter: (v: any) => formatBalance(v, symbol, 2) },
+ ]
+ } else {
+ return [
+ {
+ header: 'ID',
+ align: 'left',
+ csvOnly: false,
+ formatter: noop,
+ },
+ { header: 'Name', align: 'left', csvOnly: false, formatter: noop },
+ {
+ header: 'Market value',
+ align: 'right',
+ csvOnly: false,
+ sortable: true,
+ formatter: (v: any) => formatBalance(v, symbol, 2),
+ },
+ {
+ header: 'Face value',
+ align: 'right',
+ csvOnly: false,
+ sortable: true,
+ formatter: (v: any) => formatBalance(v, symbol, 2),
+ },
+ {
+ header: 'Quantity',
+ align: 'right',
+ csvOnly: false,
+ sortable: true,
+ formatter: (v: any) => formatBalance(v, symbol, 2),
+ },
+ {
+ header: 'Market price',
+ align: 'right',
+ csvOnly: false,
+ sortable: true,
+ formatter: (v: any) => formatBalance(v, symbol, 2),
+ },
+ { header: 'Maturity date', align: 'left', csvOnly: false, sortable: true, formatter: formatDate },
+ {
+ header: 'Unrealized profit',
+ align: 'right',
+ csvOnly: false,
+ sortable: true,
+ formatter: (v: any) => formatBalance(v, symbol, 2),
+ },
+ {
+ header: 'Realized profit',
+ align: 'right',
+ csvOnly: false,
+ sortable: true,
+ formatter: (v: any) => formatBalance(v, symbol, 2),
+ },
+ ]
+ }
}
export function AssetList({ pool }: { pool: Pool }) {
- const loans = useLoans(pool.id) as Loan[]
- const { setCsvData, loanStatus } = React.useContext(ReportContext)
+ const { loanStatus, startDate, setCsvData } = useContext(ReportContext)
+ const { data: poolMetadata } = usePoolMetadata(pool)
+ const poolCreditType = useMemo(() => poolMetadata?.pool?.asset.class || 'privateCredit', [poolMetadata])
const { symbol } = pool.currency
- const columnConfig = [
- {
- header: 'ID',
- align: 'left',
- csvOnly: false,
- formatter: noop,
- },
- {
- header: 'Status',
- align: 'left',
- csvOnly: false,
- formatter: noop,
- },
- {
- header: 'Value',
- align: 'right',
- csvOnly: false,
- sortable: true,
- formatter: (v: any) => (typeof v === 'number' ? formatBalance(v, symbol, 2) : '-'),
- },
- {
- header: 'Value currency',
- align: 'left',
- csvOnly: true,
- formatter: noop,
- },
- {
- header: 'Outstanding',
- align: 'right',
- csvOnly: false,
- sortable: true,
- formatter: (v: any) => (typeof v === 'number' ? formatBalance(v, symbol, 2) : '-'),
- },
- {
- header: 'Outstanding currency',
- align: 'left',
- csvOnly: true,
- formatter: noop,
- },
- {
- header: 'Total financed',
- align: 'right',
- csvOnly: false,
- sortable: true,
- formatter: (v: any) => (typeof v === 'number' ? formatBalance(v, symbol, 2) : '-'),
- },
- {
- header: 'Total financed currency',
- align: 'left',
- csvOnly: true,
- formatter: noop,
- },
- {
- header: 'Total repaid',
- align: 'right',
- sortable: true,
- csvOnly: false,
- formatter: (v: any) => (typeof v === 'number' ? formatBalance(v, symbol, 2) : '-'),
- },
- {
- header: 'Total repaid currency',
- align: 'left',
- csvOnly: true,
- formatter: noop,
- },
- {
- header: 'Financing date',
- align: 'left',
- sortable: true,
- csvOnly: false,
- formatter: (v: any) => (v !== '-' ? formatDate(v) : v),
- },
- {
- header: 'Maturity date',
- align: 'left',
- csvOnly: false,
- sortable: true,
- formatter: formatDate,
- },
- {
- header: 'Interest rate',
- align: 'left',
- csvOnly: false,
- formatter: (v: any) => (typeof v === 'number' ? formatPercentage(v, true, undefined, 2) : '-'),
- },
- {
- header: 'Valuation method',
- align: 'left',
- csvOnly: false,
- formatter: noop,
- },
- ]
+ const snapshots = useAllPoolAssetSnapshots(pool.id, startDate)
+ const columnConfig = useMemo(() => getColumnConfig(poolCreditType, symbol), [poolCreditType, symbol])
- const columns = columnConfig
- .map((col, index) => ({
- align: col.align,
- header: col.sortable ? : col.header,
- sortKey: col.sortable ? `value[${index}]` : undefined,
- cell: (row: TableDataRow) => {col.formatter((row.value as any)[index])},
- csvOnly: col.csvOnly,
- }))
- .filter((col) => !col.csvOnly)
+ const columns = useMemo(
+ () =>
+ columnConfig
+ .map((col, index) => ({
+ align: col.align,
+ header: col.sortable ? : col.header,
+ sortKey: col.sortable ? `value[${index}]` : undefined,
+ cell: (row: TableDataRow) => {col.formatter((row.value as any)[index])},
+ csvOnly: col.csvOnly,
+ }))
+ .filter((col) => !col.csvOnly),
+ [columnConfig]
+ )
- const data: TableDataRow[] = React.useMemo(() => {
- if (!loans) {
- return []
- }
+ const data = useMemo(() => {
+ if (!snapshots) return []
- return loans
- .filter((loan) => loan.status !== 'Created')
- .map((loan) => ({
- name: '',
- value: [
- loan.id,
- loan.status === 'Closed'
- ? 'Repaid'
- : new Date() > new Date(loan.pricing.maturityDate)
- ? loan.outstandingDebt.isZero()
- ? 'Repaid'
- : 'Overdue'
- : 'Active',
- 'presentValue' in loan ? loan.presentValue.toFloat() : '-',
- symbol,
- 'outstandingDebt' in loan ? loan.outstandingDebt.toFloat() : '-',
- symbol,
- 'totalBorrowed' in loan ? loan.totalBorrowed.toFloat() : '-',
- symbol,
- 'totalRepaid' in loan ? loan.totalRepaid.toFloat() : '-',
- symbol,
- 'originationDate' in loan ? loan.originationDate : '-',
- loan.pricing.maturityDate,
- 'interestRate' in loan.pricing ? loan.pricing.interestRate.toPercent().toNumber() : '-',
- valuationLabels[loan.pricing.valuationMethod],
- ],
- heading: false,
- }))
- .filter((row) => (loanStatus === 'all' || !loanStatus ? true : row.value[1] === loanStatus))
- }, [loans, symbol, loanStatus])
+ return snapshots
+ .filter(
+ (snapshot) =>
+ snapshot?.status !== 'Created' &&
+ snapshot?.valuationMethod?.toLowerCase() !== 'cash' &&
+ (loanStatus === 'all' || !loanStatus || snapshot?.status === loanStatus)
+ )
+ .map((snapshot) => {
+ if (poolCreditType === 'privateCredit') {
+ return {
+ name: '',
+ value: [
+ snapshot.assetId,
+ snapshot?.name,
+ snapshot?.presentValue,
+ snapshot?.outstandingPrincipal,
+ snapshot?.outstandingInterest,
+ snapshot?.totalRepaidPrincipal,
+ snapshot?.totalRepaidInterest,
+ snapshot?.totalRepaidUnscheduled,
+ snapshot?.actualOriginationDate,
+ snapshot?.actualMaturityDate,
+ valuationLabels[snapshot?.valuationMethod] || snapshot?.valuationMethod,
+ snapshot?.advanceRate,
+ snapshot?.collateralValue,
+ snapshot?.probabilityOfDefault,
+ snapshot?.lossGivenDefault,
+ snapshot?.discountRate,
+ ],
+ heading: false,
+ }
+ } else {
+ return {
+ name: '',
+ value: [
+ snapshot?.assetId,
+ snapshot?.name,
+ snapshot?.presentValue,
+ snapshot.faceValue,
+ snapshot?.outstandingQuantity,
+ snapshot?.currentPrice,
+ snapshot?.actualMaturityDate,
+ snapshot?.unrealizedProfitAtMarketPrice,
+ snapshot?.sumRealizedProfitFifo,
+ ],
+ heading: false,
+ }
+ }
+ })
+ }, [snapshots, poolCreditType, symbol, loanStatus])
- React.useEffect(() => {
- if (!data.length) {
+ useEffect(() => {
+ if (!snapshots?.length) {
return
}
@@ -182,10 +241,9 @@ export function AssetList({ pool }: { pool: Pool }) {
setCsvData(undefined)
URL.revokeObjectURL(dataUrl)
}
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [data])
+ }, [snapshots])
- if (!loans) {
+ if (!snapshots) {
return
}