Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pool Overview redesign #2435

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
404 changes: 306 additions & 98 deletions centrifuge-app/src/components/Charts/PoolPerformanceChart.tsx

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions centrifuge-app/src/components/Charts/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ export function TooltipContainer({ children }: { children: React.ReactNode }) {
bg="backgroundPage"
p={1}
style={{
boxShadow: '1px 3px 6px rgba(0, 0, 0, .15)',
boxShadow: '1px 3px 6px 0px rgba(0, 0, 0, 0.15)',
}}
minWidth="180px"
minWidth="250px"
gap="4px"
>
{children}
Expand All @@ -44,7 +44,7 @@ export function TooltipContainer({ children }: { children: React.ReactNode }) {

export function TooltipTitle({ children }: { children: React.ReactNode }) {
return (
<Text variant="label2" fontWeight="500">
<Text color="textPrimary" variant="label2" fontWeight="500">
{children}
</Text>
)
Expand Down
7 changes: 5 additions & 2 deletions centrifuge-app/src/components/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export type DataTableProps<T = any> = {
footer?: React.ReactNode
pageSize?: number
page?: number
headerStyles?: React.CSSProperties
} & GroupedProps

export type OrderBy = 'asc' | 'desc'
Expand Down Expand Up @@ -96,6 +97,7 @@ export const DataTable = <T extends Record<string, any>>({
defaultSortOrder = 'desc',
pageSize = Infinity,
page = 1,
headerStyles,
}: DataTableProps<T>) => {
const [orderBy, setOrderBy] = React.useState<Record<string, OrderBy>>(
defaultSortKey ? { [defaultSortKey]: defaultSortOrder } : {}
Expand All @@ -122,7 +124,7 @@ export const DataTable = <T extends Record<string, any>>({
return (
<TableGrid gridTemplateColumns={templateColumns} gridAutoRows="auto" gap={0} rowGap={0}>
{showHeader && (
<HeaderRow>
<HeaderRow styles={headerStyles}>
{columns.map((col, i) => (
<HeaderCol key={i} align={col?.align}>
<Text variant="body3">
Expand Down Expand Up @@ -203,12 +205,13 @@ const Row = styled('div')`
box-shadow: ${({ theme }) => `-1px 0 0 0 ${theme.colors.borderPrimary}, 1px 0 0 0 ${theme.colors.borderPrimary}`};
`

const HeaderRow = styled(Row)<any>(
const HeaderRow = styled(Row)<{ styles?: any }>(({ styles }) =>
css({
backgroundColor: 'backgroundSecondary',
borderStyle: 'solid',
borderWidth: '1px 0',
borderColor: 'borderPrimary',
...styles,
})
)

Expand Down
271 changes: 165 additions & 106 deletions centrifuge-app/src/components/IssuerSection.tsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,121 @@
import { PoolMetadata } from '@centrifuge/centrifuge-js'
import { useCentrifuge } from '@centrifuge/centrifuge-react'
import { Accordion, AnchorButton, Box, IconExternalLink, Shelf, Stack, Text } from '@centrifuge/fabric'
import {
AnchorButton,
Box,
IconBalanceSheet,
IconCashflow,
IconChevronRight,
IconExternalLink,
IconProfitAndLoss,
Shelf,
Stack,
Text,
} from '@centrifuge/fabric'
import * as React from 'react'
import { useLocation } from 'react-router'
import styled from 'styled-components'
import { ExecutiveSummaryDialog } from './Dialogs/ExecutiveSummaryDialog'
import { LabelValueStack } from './LabelValueStack'
import { PillButton } from './PillButton'
import { AnchorTextLink } from './TextLink'
import { AnchorPillButton, PillButton } from './PillButton'
import { RouterTextLink } from './TextLink'

const SUBTLE_GRAY = '#91969b21'

type IssuerSectionProps = {
metadata: Partial<PoolMetadata> | undefined
}

const reportLinks = [
{ label: 'Balance sheet', href: '/balance-sheet', icon: <IconBalanceSheet /> },
{ label: 'Profit & loss', href: '/profit-and-loss', icon: <IconProfitAndLoss /> },
{ label: 'Cashflow statement', href: '/cash-flow-statement', icon: <IconCashflow /> },
]

const StyledRouterTextLink = styled(RouterTextLink)`
color: white;
text-decoration: unset;
font-size: 14px;
:active {
color: white;
}
:visited {
color: white;
}
`

export function ReportDetails({ metadata }: IssuerSectionProps) {
const cent = useCentrifuge()
const pathname = useLocation().pathname
const report = metadata?.pool?.reports?.[0]

return (
<Stack gap={2}>
<Text variant="heading2">Pool analysis</Text>
<Shelf flexDirection="column" alignItems="flex-start">
{report && (
<>
<Shelf gap={1}>
{report.author.avatar?.uri && (
<Box
as="img"
height={40}
borderRadius={30}
src={cent.metadata.parseMetadataUrl(report.author.avatar.uri)}
alt=""
/>
)}
{report.author.name && (
<LabelValueStack label="Reviewer" value={<Text variant="body2">{report.author.name}</Text>} />
)}
{report.author.title && (
<LabelValueStack label="Reviewer title" value={<Text variant="body2">{report.author.title}</Text>} />
)}
</Shelf>
<Shelf marginTop={20}>
<AnchorButton href={report.uri} target="_blank" variant="secondary" icon={IconExternalLink}>
View full analysis
</AnchorButton>
</Shelf>
</>
)}
</Shelf>
</Stack>
<>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Text color="white" variant="heading4">
Reports
</Text>
<Box backgroundColor={SUBTLE_GRAY} padding="8px 22px" borderRadius="4px">
<StyledRouterTextLink to={`${pathname}/reporting`}>View all</StyledRouterTextLink>
</Box>
</Box>

<Box marginY={2} backgroundColor={SUBTLE_GRAY} padding={2} borderRadius={10}>
{reportLinks.map((link, i) => (
<Box
borderBottom={i === reportLinks.length - 1 ? null : `2px solid ${SUBTLE_GRAY}`}
display="flex"
alignItems="center"
justifyContent="space-between"
paddingY={3}
>
<Box display="flex" alignItems="center">
{link.icon}
<StyledRouterTextLink
style={{ marginLeft: 8 }}
to={`${pathname}/reporting${link.href}`}
key={`${link.label}-${i}`}
>
{link.label}
</StyledRouterTextLink>
</Box>
<IconChevronRight color="white" />
</Box>
))}
</Box>

{report && <PoolAnalysis metadata={metadata} />}
</>
)
}

export function IssuerDetails({ metadata }: IssuerSectionProps) {
const cent = useCentrifuge()
const [isDialogOpen, setIsDialogOpen] = React.useState(false)

const links = [
{
label: 'Website',
href: metadata?.pool?.links.website,
show: !!metadata?.pool?.links.website,
},
{
label: 'Forum',
href: metadata?.pool?.links.forum,
show: !!metadata?.pool?.links.forum,
},
{
label: 'Email',
href: `mailto:${metadata?.pool?.issuer.email}`,
show: !!metadata?.pool?.issuer.email,
},
{
label: 'Executive Summary',
show: !!metadata?.pool?.links.executiveSummary,
onClick: () => setIsDialogOpen(true),
},
]
return (
<Stack gap={2}>
<Stack gap={1}>
<Stack>
<Shelf display="flex" justifyContent="space-between" marginBottom={12}>
{metadata?.pool?.issuer.logo && (
<Box
as="img"
Expand All @@ -65,83 +125,63 @@ export function IssuerDetails({ metadata }: IssuerSectionProps) {
src={cent.metadata.parseMetadataUrl(metadata?.pool?.issuer.logo?.uri)}
/>
)}
<LabelValueStack label="Issuer" value={<Text variant="body2">{metadata?.pool?.issuer.name}</Text>} />
<LabelValueStack
label="Legal representative"
value={<Text variant="body2">{metadata?.pool?.issuer.repName}</Text>}
/>
<LabelValueStack
label="Short description"
value={<Text variant="body2">{metadata?.pool?.issuer.shortDescription}</Text>}
/>
<LabelValueStack
label="Description"
value={<Text variant="body2">{metadata?.pool?.issuer.description}</Text>}
/>
{metadata?.pool?.links.executiveSummary && (
<LabelValueStack
label="Download"
value={
<>
<PillButton variant="small" onClick={() => setIsDialogOpen(true)}>
Executive summary
</PillButton>
<ExecutiveSummaryDialog
issuerName={metadata?.pool?.issuer.name}
href={cent.metadata.parseMetadataUrl(metadata?.pool?.links.executiveSummary?.uri)}
open={isDialogOpen}
onClose={() => setIsDialogOpen(false)}
/>
</>
}
/>
)}
</Stack>

{(metadata?.pool?.links.website || metadata?.pool?.links.forum || metadata?.pool?.issuer.email) && (
<LabelValueStack
label="Links"
value={
<Text variant="body3">
<Shelf flexWrap="wrap" gap={2} alignItems="flex-start">
{metadata?.pool?.links.website && (
<AnchorTextLink href={metadata?.pool?.links.website}>Website</AnchorTextLink>
)}
{metadata?.pool?.links.forum && (
<AnchorTextLink href={metadata?.pool?.links.forum}>Forum</AnchorTextLink>
)}
{metadata?.pool?.issuer.email && (
<AnchorTextLink href={`mailto:${metadata?.pool?.issuer.email}`}>Email</AnchorTextLink>
)}
</Shelf>
</Text>
}
/>
)}
{!!metadata?.pool?.details?.length && (
<LabelValueStack label="Details" value={<Accordion items={metadata?.pool?.details} />} />
)}
<Links links={links} />
</Shelf>
<Box pt={4}>
<Text variant="heading2">{metadata?.pool?.name}</Text>
<Text variant="body2" style={{ marginTop: '12px' }}>
{metadata?.pool?.issuer.description}
</Text>
</Box>
<ExecutiveSummaryDialog
issuerName={metadata?.pool?.issuer.name ?? ''}
href={cent.metadata.parseMetadataUrl(metadata?.pool?.links.executiveSummary?.uri ?? '')}
open={isDialogOpen}
onClose={() => setIsDialogOpen(false)}
/>
</Stack>
)
}

const Links = ({ links }: { links: { label: string; href?: string; show: boolean; onClick?: () => void }[] }) => {
return (
<Box display="flex">
{links.map((link, index) => {
if (!link.show) return null

if (link.onClick) {
return (
<PillButton key={`${link.label} ${index}`} variant="small" onClick={link.onClick}>
{link.label}
</PillButton>
)
}

return (
<AnchorPillButton style={{ marginRight: 8 }} variant="small" key={`${link.label} ${index}`} href={link.href}>
{link.label}
</AnchorPillButton>
)
})}
</Box>
)
}

export function RatingDetails({ metadata }: IssuerSectionProps) {
const rating = metadata?.pool?.rating

return (
return rating?.ratingAgency || rating?.ratingValue || rating?.ratingReportUrl ? (
<Stack gap={1}>
<Text variant="heading2">Pool rating</Text>
<Shelf flexDirection="column" alignItems="flex-start">
{rating && (
<Shelf gap={1}>
{rating.ratingAgency && (
<LabelValueStack label="Rating agency" value={<Text variant="body2">{rating.ratingAgency}</Text>} />
)}
{rating.ratingValue && (
<LabelValueStack label="Rating" value={<Text variant="body2">{rating.ratingValue}</Text>} />
)}
</Shelf>
)}
<Shelf gap={1}>
{rating.ratingAgency && (
<LabelValueStack label="Rating agency" value={<Text variant="body2">{rating.ratingAgency}</Text>} />
)}
{rating.ratingValue && (
<LabelValueStack label="Rating" value={<Text variant="body2">{rating.ratingValue}</Text>} />
)}
</Shelf>
</Shelf>
<Shelf>
{rating?.ratingReportUrl && (
Expand All @@ -151,5 +191,24 @@ export function RatingDetails({ metadata }: IssuerSectionProps) {
)}
</Shelf>
</Stack>
)
) : null
}

export const PoolAnalysis = ({ metadata, inverted }: IssuerSectionProps & { inverted?: boolean }) => {
const report = metadata?.pool?.reports?.[0]
return report?.author?.name || report?.author?.title ? (
<Stack gap={1}>
<Text color={inverted ? 'textPrimary' : 'white'} variant={inverted ? 'heading2' : 'heading4'}>
Pool analysis
</Text>
<Stack gap={0}>
<Text variant="body3" color="textSecondary">
Reviewer: {report?.author?.name || 'N/A'}
</Text>
<Text variant="body3" color="textSecondary">
Title: {report?.author?.title || 'N/A'}
</Text>
</Stack>
</Stack>
) : null
}
Loading
Loading