212 lines
4.8 KiB
TypeScript
212 lines
4.8 KiB
TypeScript
import type { FC } from "react";
|
|
|
|
import {
|
|
Badge,
|
|
Group,
|
|
Skeleton,
|
|
Stack,
|
|
Text,
|
|
} from "@mantine/core";
|
|
import { LoadingSkeleton } from "@wealthledger/ui";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
import {
|
|
InstrumentChart,
|
|
InstrumentEvents,
|
|
InstrumentHeader,
|
|
InstrumentMetric,
|
|
InstrumentMetrics,
|
|
InstrumentPage,
|
|
InstrumentPosition,
|
|
InstrumentPrice,
|
|
InstrumentQuoteStatus,
|
|
InstrumentTerms,
|
|
} from "@/features/instrument-details";
|
|
import {
|
|
formatDate,
|
|
formatDecimal,
|
|
formatMoney,
|
|
} from "@/shared/lib";
|
|
|
|
import type { BondPageVm } from "./BondPage.vm";
|
|
|
|
import { BondCoupons } from "./components/BondCoupons.view";
|
|
import { BondMetrics } from "./components/BondMetrics.view";
|
|
|
|
type BondPageViewProps = { vm: BondPageVm };
|
|
|
|
const BondPageView: FC<BondPageViewProps> = (props) => {
|
|
const { vm } = props;
|
|
const { t } = useTranslation();
|
|
const bond = vm.bond;
|
|
if (!bond) return;
|
|
|
|
const subtitle = `${bond.ticker ?? ""} · ${bond.isin ?? ""}`;
|
|
|
|
const maturity = bond.maturityDate
|
|
? formatDate(bond.maturityDate)
|
|
: t("bond.unknown");
|
|
|
|
const maturityDate = bond.perpetual ? t("bond.perpetual") : maturity;
|
|
|
|
const callDate = bond.callDate
|
|
? formatDate(bond.callDate)
|
|
: t("bond.unknown");
|
|
|
|
const couponFrequency
|
|
= bond.couponQuantityPerYear == undefined
|
|
? t("bond.unknown")
|
|
: String(bond.couponQuantityPerYear);
|
|
|
|
const lotSize = bond.lot == undefined ? t("bond.unknown") : String(bond.lot);
|
|
|
|
let quoteContent;
|
|
|
|
if (vm.quoteState.loading) {
|
|
quoteContent = (
|
|
<LoadingSkeleton label={t("stock.loadingPrice")}>
|
|
<Skeleton h={55} mt="lg" w="50%" />
|
|
</LoadingSkeleton>
|
|
);
|
|
}
|
|
else {
|
|
quoteContent = (
|
|
<Stack aria-live="polite" gap="xs" mt="lg">
|
|
<Text c="dimmed" size="sm">
|
|
{t("bond.percentOfNominal")}
|
|
</Text>
|
|
|
|
<InstrumentPrice>
|
|
{vm.quote?.pricePercent == undefined
|
|
? t("bond.unknown")
|
|
: `${formatDecimal(vm.quote.pricePercent)} %`}
|
|
</InstrumentPrice>
|
|
|
|
{vm.quote?.changePercentagePoints != undefined && (
|
|
<Text fw={600}>
|
|
{formatDecimal(vm.quote.changePercentagePoints)}{" "}
|
|
{t("bond.percentagePoints")}
|
|
</Text>
|
|
)}
|
|
|
|
<Text c="dimmed" size="xs">
|
|
{vm.quote?.previousCloseDate
|
|
? t("stock.changeBase", {
|
|
date: formatDate(vm.quote.previousCloseDate),
|
|
})
|
|
: t("stock.noComparison")}
|
|
</Text>
|
|
|
|
<Group gap="xl" my="sm">
|
|
<InstrumentMetric
|
|
label={t("bond.cleanPrice")}
|
|
value={formatMoney(vm.quote?.cleanPrice)}
|
|
/>
|
|
|
|
<InstrumentMetric
|
|
label={t("bond.aci")}
|
|
value={formatMoney(vm.quote?.aci)}
|
|
/>
|
|
|
|
<InstrumentMetric
|
|
label={t("bond.dirtyPrice")}
|
|
value={formatMoney(vm.quote?.dirtyPrice)}
|
|
/>
|
|
</Group>
|
|
|
|
<Text c="dimmed" size="xs">
|
|
{t("bond.indicativePrice")}
|
|
</Text>
|
|
|
|
<Group>
|
|
<InstrumentQuoteStatus quote={vm.quote} />
|
|
</Group>
|
|
|
|
{!!vm.quote?.unavailable.length && (
|
|
<Text c="dimmed" size="xs">
|
|
{t("stock.partialQuote")}
|
|
</Text>
|
|
)}
|
|
</Stack>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<InstrumentPage>
|
|
<InstrumentHeader
|
|
logoSrc={vm.logoSrc}
|
|
name={bond.name}
|
|
subtitle={subtitle}
|
|
>
|
|
|
|
<Group gap="xs" mt="md">
|
|
{!!bond.floatingCoupon && <Badge variant="light">{t("bond.floatingCoupon")}</Badge>}
|
|
|
|
{!!bond.amortization && <Badge variant="light">{t("bond.amortization")}</Badge>}
|
|
|
|
{!!bond.perpetual && <Badge variant="light">{t("bond.perpetual")}</Badge>}
|
|
|
|
{!!bond.subordinated && (
|
|
<Badge color="orange" variant="light">
|
|
{t("bond.subordinated")}
|
|
</Badge>
|
|
)}
|
|
</Group>
|
|
|
|
{quoteContent}
|
|
|
|
<InstrumentTerms title={t("bond.terms")}>
|
|
|
|
<InstrumentMetrics>
|
|
<InstrumentMetric
|
|
label={t("bond.nominal")}
|
|
value={formatMoney(bond.nominal)}
|
|
/>
|
|
|
|
<InstrumentMetric
|
|
label={t("bond.initialNominal")}
|
|
value={formatMoney(bond.initialNominal)}
|
|
/>
|
|
|
|
<InstrumentMetric label={t("bond.maturity")} value={maturityDate} />
|
|
|
|
<InstrumentMetric label={t("bond.callDate")} value={callDate} />
|
|
|
|
<InstrumentMetric label={t("bond.couponFrequency")} value={couponFrequency} />
|
|
|
|
<InstrumentMetric label={t("bond.lot")} value={lotSize} />
|
|
</InstrumentMetrics>
|
|
</InstrumentTerms>
|
|
</InstrumentHeader>
|
|
|
|
<BondMetrics metrics={vm.metrics} state={vm.metricsState} />
|
|
|
|
<InstrumentChart priceUnit="percent" vm={vm} />
|
|
|
|
<InstrumentPosition
|
|
quantityLabel={t("bond.quantity")}
|
|
returnNote={t("bond.returnNote")}
|
|
vm={vm}
|
|
/>
|
|
|
|
<BondCoupons
|
|
coupons={vm.coupons}
|
|
logoName={bond.logoName}
|
|
pastCoupons={vm.pastCoupons}
|
|
state={vm.couponsState}
|
|
ticker={bond.ticker}
|
|
upcoming={vm.upcoming}
|
|
/>
|
|
|
|
<InstrumentEvents
|
|
historyNote={t("bond.historyNote")}
|
|
incomeFilter="coupons"
|
|
incomeLabel={t("bond.coupons")}
|
|
vm={vm}
|
|
/>
|
|
</InstrumentPage>
|
|
);
|
|
};
|
|
|
|
export { BondPageView };
|