Files
frontend/src/pages/bond/ui/BondPage/BondPage.view.tsx
T
codex-bot 43b0f5574a
Frontend verify / commits (push) Skipped
Frontend verify / security (push) Successful in 10s
Frontend verify / verify (push) Successful in 7m18s
feat: unify income payment cards and remove comparisons (WealthLedger/main#33) (#74)
feat: unify income payment cards and remove comparisons (WealthLedger/main#33)
2026-10-13 18:17:35 +03:00

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 };