From a404c103c1138883cf65009ba1a9ffe7e3017a4e Mon Sep 17 00:00:00 2001 From: katty barroso Date: Tue, 14 Jan 2025 15:10:11 +0100 Subject: [PATCH] Add data table for orders --- .../src/components/Report/DataFilter.tsx | 9 +- .../src/components/Report/Orders.tsx | 111 ++++++++++++++++++ .../src/components/Report/ReportContext.tsx | 1 + .../src/components/Report/index.tsx | 2 + centrifuge-app/src/utils/date.ts | 13 ++ centrifuge-app/src/utils/usePools.ts | 5 + centrifuge-js/src/modules/pools.ts | 89 +++++++++++++- centrifuge-js/src/types/subquery.ts | 26 ++++ 8 files changed, 253 insertions(+), 3 deletions(-) create mode 100644 centrifuge-app/src/components/Report/Orders.tsx diff --git a/centrifuge-app/src/components/Report/DataFilter.tsx b/centrifuge-app/src/components/Report/DataFilter.tsx index 370d983287..0dac1edf63 100644 --- a/centrifuge-app/src/components/Report/DataFilter.tsx +++ b/centrifuge-app/src/components/Report/DataFilter.tsx @@ -60,6 +60,7 @@ export function DataFilter({ poolId }: ReportFilterProps) { { label: 'Token price', value: 'token-price' }, { label: 'Asset list', value: 'asset-list' }, { label: 'Investor list', value: 'investor-list' }, + { label: 'Orders', value: 'orders' }, ] return ( @@ -228,8 +229,12 @@ export function DataFilter({ poolId }: ReportFilterProps) { /> )} - setStartDate(e.target.value)} /> - setEndDate(e.target.value)} /> + {report !== 'orders' && ( + <> + setStartDate(e.target.value)} /> + setEndDate(e.target.value)} /> + + )} diff --git a/centrifuge-app/src/components/Report/Orders.tsx b/centrifuge-app/src/components/Report/Orders.tsx new file mode 100644 index 0000000000..deb3a6bda7 --- /dev/null +++ b/centrifuge-app/src/components/Report/Orders.tsx @@ -0,0 +1,111 @@ +import { Pool } from '@centrifuge/centrifuge-js/dist/modules/pools' +import { Box, Text } from '@centrifuge/fabric' +import { useMemo } from 'react' +import { TableDataRow } from '.' +import { formatDateAndTime } from '../../../src/utils/date' +import { formatBalance } from '../../../src/utils/formatting' +import { usePoolOrdersByPoolId } from '../../../src/utils/usePools' +import { DataTable, SortableTableHeader } from '../DataTable' + +const noop = (v: any) => v + +const Orders = ({ pool }: { pool: Pool }) => { + const orders = usePoolOrdersByPoolId(pool.id) + + const columnsConfig = [ + { + align: 'left', + header: 'Epoch', + sortable: true, + formatter: noop, + }, + { + align: 'left', + header: 'Date & Time', + sortable: true, + formatter: (v: any) => formatDateAndTime(v), + width: '200px', + }, + { + align: 'left', + header: 'NAV', + sortable: true, + formatter: (v: any) => (v ? formatBalance(v, undefined, pool.currency.decimals) : '-'), + }, + { + align: 'left', + header: 'Nav per share', + sortable: true, + formatter: (v: any) => (v ? formatBalance(v, undefined, pool.currency.decimals) : '-'), + }, + { + align: 'left', + header: 'Investments locked', + sortable: true, + formatter: (v: any) => (v ? formatBalance(v, pool.currency.symbol, 2) : '-'), + }, + { + align: 'left', + header: 'Investments executed', + sortable: true, + formatter: (v: any) => (v ? formatBalance(v, pool.currency.symbol, 2) : '-'), + }, + { + align: 'left', + header: 'Redemptions locked', + sortable: true, + formatter: (v: any) => (v ? formatBalance(v, pool.currency.symbol, 2) : '-'), + }, + { + align: 'left', + header: 'Redemptions executed', + sortable: true, + formatter: (v: any) => (v ? formatBalance(v, pool.currency.symbol, 2) : '-'), + }, + { + align: 'left', + header: 'Paid fees', + sortable: true, + formatter: (v: any) => (v ? formatBalance(v, pool.currency.symbol, 2) : '-'), + }, + ] + + const columns = columnsConfig.map((col, index) => ({ + align: col.align, + header: col.sortable ? : col.header, + cell: (row: TableDataRow) => { + return {col.formatter((row.value as any)[index])} + }, + sortKey: col.sortable ? `value[${index}]` : undefined, + width: col.width, + })) + + const data = useMemo(() => { + if (!orders?.length) return [] + else { + return orders.map((order) => ({ + name: '', + value: [ + order.epochId, + order.closedAt, + order.netAssetValue, + order.tokenPrice, + order.sumOutstandingInvestOrders, + order.sumFulfilledInvestOrders, + order.sumOutstandingRedeemOrders, + order.sumFulfilledRedeemOrders, + order.paidFees, + ], + heading: false, + })) + } + }, [orders]) + + return ( + + + + ) +} + +export default Orders diff --git a/centrifuge-app/src/components/Report/ReportContext.tsx b/centrifuge-app/src/components/Report/ReportContext.tsx index 5ef1b8ab6a..a0af7b3c32 100644 --- a/centrifuge-app/src/components/Report/ReportContext.tsx +++ b/centrifuge-app/src/components/Report/ReportContext.tsx @@ -17,6 +17,7 @@ export type Report = | 'balance-sheet' | 'cash-flow-statement' | 'profit-and-loss' + | 'orders' export type ReportContextType = { csvData?: CsvDataProps diff --git a/centrifuge-app/src/components/Report/index.tsx b/centrifuge-app/src/components/Report/index.tsx index 2cb980da04..da842039c9 100644 --- a/centrifuge-app/src/components/Report/index.tsx +++ b/centrifuge-app/src/components/Report/index.tsx @@ -10,6 +10,7 @@ import { FeeTransactions } from './FeeTransactions' import { InvestorList } from './InvestorList' import { InvestorTransactions } from './InvestorTransactions' import { OracleTransactions } from './OracleTransactions' +import Orders from './Orders' import { PoolBalance } from './PoolBalance' import { ProfitAndLoss } from './ProfitAndLoss' import { ReportContext } from './ReportContext' @@ -38,6 +39,7 @@ export function ReportComponent({ pool }: { pool: Pool }) { {report === 'cash-flow-statement' && } {report === 'oracle-tx' && } {report === 'profit-and-loss' && } + {report === 'orders' && } ) diff --git a/centrifuge-app/src/utils/date.ts b/centrifuge-app/src/utils/date.ts index 2df7487773..dfe27a628c 100644 --- a/centrifuge-app/src/utils/date.ts +++ b/centrifuge-app/src/utils/date.ts @@ -8,6 +8,19 @@ export function formatDate(timestamp: number | string | Date, options?: Intl.Dat }) } +export function formatDateAndTime(timestamp: number | string | Date, options?: Intl.DateTimeFormatOptions) { + return new Date(timestamp).toLocaleString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + timeZone: 'UTC', + ...options, + }) +} + export function formatDateTechnical(timestamp: number | string) { return new Date(timestamp).toLocaleDateString('en-US') } diff --git a/centrifuge-app/src/utils/usePools.ts b/centrifuge-app/src/utils/usePools.ts index f8771a92fe..a7c302ef58 100644 --- a/centrifuge-app/src/utils/usePools.ts +++ b/centrifuge-app/src/utils/usePools.ts @@ -265,6 +265,11 @@ export function usePoolOrders(poolId: string) { return result } +export function usePoolOrdersByPoolId(poolId: string) { + const [result] = useCentrifugeQuery(['poolOrdersByPoolId', poolId], (cent) => cent.pools.getPoolOrdersById([poolId])) + return result +} + export function useOrder(poolId: string, trancheId: string, address?: string) { const [result] = useCentrifugeQuery( ['order', trancheId, address], diff --git a/centrifuge-js/src/modules/pools.ts b/centrifuge-js/src/modules/pools.ts index f2946a11a4..7b9b5debdf 100644 --- a/centrifuge-js/src/modules/pools.ts +++ b/centrifuge-js/src/modules/pools.ts @@ -25,6 +25,7 @@ import { SubqueryPoolAssetSnapshot, SubqueryPoolFeeSnapshot, SubqueryPoolFeeTransaction, + SubqueryPoolOrdersById, SubqueryPoolSnapshot, SubqueryTrancheBalances, SubqueryTrancheSnapshot, @@ -2443,6 +2444,11 @@ export function getPoolsModule(inst: Centrifuge) { tranche { poolId trancheId + pool { + currency{ + decimals + } + } } timestamp tokenSupply @@ -2501,13 +2507,14 @@ export function getPoolsModule(inst: Centrifuge) { map(({ trancheSnapshots }) => { const trancheStates: Record< string, - { timestamp: string; tokenPrice: Price; yield30DaysAnnualized: Perquintill }[] + { timestamp: string; tokenPrice: Price; yield30DaysAnnualized: Perquintill; tokenSupply: TokenBalance }[] > = {} trancheSnapshots?.forEach((state) => { const tid = state.tranche.trancheId const entry = { timestamp: state.timestamp, tokenPrice: new Price(state.tokenPrice), + tokenSupply: new TokenBalance(state.tokenSupply, state.tranche.pool.currency.decimals), pool: state.tranche.poolId, yield30DaysAnnualized: state.yield30DaysAnnualized ? new Perquintill(state.yield30DaysAnnualized) @@ -3941,6 +3948,85 @@ export function getPoolsModule(inst: Centrifuge) { ) } + function getPoolOrdersById(args: [poolId: string]) { + const [poolId] = args + + const $query = inst.getSubqueryObservable<{ + epoches: { nodes: SubqueryPoolOrdersById[] } + }>( + `query($poolId: String!) { + epoches( + filter: { + poolId: { equalTo: $poolId } + } + ) { + nodes { + poolId + id + sumPoolFeesPaidAmount + closedAt + epochStates{ + nodes{ + tokenPrice + sumOutstandingInvestOrders + sumFulfilledInvestOrders + sumOutstandingRedeemOrders + sumFulfilledRedeemOrders + } + } + poolSnapshots{ + nodes{ + netAssetValue + } + } + } + } + } + `, + { + poolId, + }, + false + ) + + return $query.pipe( + combineLatestWith(getPoolCurrency([poolId])), + map(([data, poolCurrency]) => { + return data?.epoches?.nodes.map((order) => { + const index = order.epochStates.nodes.length - 1 + return { + epochId: order.id, + closedAt: order.closedAt, + paidFees: order.sumPoolFeesPaidAmount + ? new CurrencyBalance(order.sumPoolFeesPaidAmount, poolCurrency.decimals) + : null, + tokenPrice: order.epochStates.nodes[index].tokenPrice + ? new CurrencyBalance(order.epochStates.nodes[index].tokenPrice, poolCurrency.decimals) + : null, + sumOutstandingInvestOrders: order.epochStates.nodes[index].sumOutstandingInvestOrders + ? new CurrencyBalance(order.epochStates.nodes[index].sumOutstandingInvestOrders, poolCurrency.decimals) + : null, + sumFulfilledInvestOrders: order.epochStates.nodes[index].sumFulfilledInvestOrders + ? new CurrencyBalance(order.epochStates.nodes[index].sumFulfilledInvestOrders, poolCurrency.decimals) + : null, + sumOutstandingRedeemOrders: order.epochStates.nodes[index].sumOutstandingRedeemOrders + ? new CurrencyBalance(order.epochStates.nodes[index].sumOutstandingRedeemOrders, poolCurrency.decimals) + : null, + sumFulfilledRedeemOrders: order.epochStates.nodes[index].sumFulfilledRedeemOrders + ? new CurrencyBalance(order.epochStates.nodes[index].sumFulfilledRedeemOrders, poolCurrency.decimals) + : null, + netAssetValue: order.poolSnapshots.nodes.length + ? new CurrencyBalance( + order.poolSnapshots.nodes[order.poolSnapshots.nodes.length - 1].netAssetValue, + poolCurrency.decimals + ) + : null, + } + }) + }) + ) + } + function getLoans(args: [poolId: string]) { const [poolId] = args const $api = inst.getApi() @@ -4631,6 +4717,7 @@ export function getPoolsModule(inst: Centrifuge) { getBalances, getOrder, getPoolOrders, + getPoolOrdersById, getPoolAccountOrders, getPortfolio, getLoans, diff --git a/centrifuge-js/src/types/subquery.ts b/centrifuge-js/src/types/subquery.ts index 520f3e28bd..6949de0f45 100644 --- a/centrifuge-js/src/types/subquery.ts +++ b/centrifuge-js/src/types/subquery.ts @@ -38,6 +38,11 @@ export type SubqueryTrancheSnapshot = { tranche: { poolId: string trancheId: string + pool: { + currency: { + decimals: number + } + } } tokenSupply: string sumOutstandingInvestOrdersByPeriod: string @@ -192,6 +197,27 @@ export type SubqueryPoolAssetSnapshot = { totalRepaidUnscheduled: string | undefined } +export type SubqueryPoolOrdersById = { + __typename?: 'Epoches' + id: string + sumPoolFeesPaidAmount: string + closedAt: string + epochStates: { + nodes: { + tokenPrice: string + sumOutstandingInvestOrders: string + sumFulfilledInvestOrders: string + sumOutstandingRedeemOrders: string + sumFulfilledRedeemOrders: string + }[] + } + poolSnapshots: { + nodes: { + netAssetValue: string + }[] + } +} + export type PoolFeeTransactionType = 'PROPOSED' | 'ADDED' | 'REMOVED' | 'CHARGED' | 'UNCHARGED' | 'PAID' | 'ACCRUED' export type SubqueryPoolFeeTransaction = {