refactor: tighten types and reuse instrument UI
This commit is contained in:
@@ -1,33 +0,0 @@
|
||||
.page {
|
||||
max-width: 1120px;
|
||||
margin: 0 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
.price {
|
||||
font-size: clamp(2rem, 4vw, 3.2rem);
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.04em;
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 1.2;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.companyMetrics {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(135px, 1fr));
|
||||
gap: var(--mantine-spacing-lg);
|
||||
}
|
||||
.embeddedSection {
|
||||
margin-top: var(--mantine-spacing-lg);
|
||||
padding-top: var(--mantine-spacing-lg);
|
||||
border-top: 1px solid var(--wl-color-border);
|
||||
}
|
||||
.companyMetrics > div {
|
||||
padding: var(--mantine-spacing-sm);
|
||||
background: var(--wl-color-subtle);
|
||||
border-radius: var(--mantine-radius-md);
|
||||
}
|
||||
@media (max-width: 48em) {
|
||||
.companyMetrics {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
@@ -14,23 +14,23 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import {
|
||||
InstrumentChart,
|
||||
instrumentDetailsStyles,
|
||||
InstrumentEvents,
|
||||
InstrumentHeader,
|
||||
InstrumentMetric,
|
||||
InstrumentPosition,
|
||||
InstrumentQuoteStatus,
|
||||
} from "@/features/instrument-details";
|
||||
import {
|
||||
formatDate,
|
||||
formatDecimal,
|
||||
formatMoney,
|
||||
formatSnapshotTime,
|
||||
} from "@/shared/lib";
|
||||
|
||||
import type { BondPageVm } from "./BondPage.vm";
|
||||
|
||||
import classes from "./BondPage.module.css";
|
||||
import { BondCoupons } from "./components/BondCoupons.view";
|
||||
import { BondMetrics } from "./components/BondMetrics.view";
|
||||
import { Metric } from "./components/Metric.view";
|
||||
|
||||
type BondPageViewProps = { vm: BondPageVm };
|
||||
|
||||
@@ -75,7 +75,7 @@ const BondPageView: FC<BondPageViewProps> = (props) => {
|
||||
{t("bond.percentOfNominal")}
|
||||
</Text>
|
||||
|
||||
<Text className={classes.price}>
|
||||
<Text className={instrumentDetailsStyles.price}>
|
||||
{vm.quote?.pricePercent == undefined
|
||||
? t("bond.unknown")
|
||||
: `${formatDecimal(vm.quote.pricePercent)} %`}
|
||||
@@ -97,17 +97,17 @@ const BondPageView: FC<BondPageViewProps> = (props) => {
|
||||
</Text>
|
||||
|
||||
<Group gap="xl" my="sm">
|
||||
<Metric
|
||||
<InstrumentMetric
|
||||
label={t("bond.cleanPrice")}
|
||||
value={formatMoney(vm.quote?.cleanPrice)}
|
||||
/>
|
||||
|
||||
<Metric
|
||||
<InstrumentMetric
|
||||
label={t("bond.aci")}
|
||||
value={formatMoney(vm.quote?.aci)}
|
||||
/>
|
||||
|
||||
<Metric
|
||||
<InstrumentMetric
|
||||
label={t("bond.dirtyPrice")}
|
||||
value={formatMoney(vm.quote?.dirtyPrice)}
|
||||
/>
|
||||
@@ -118,19 +118,7 @@ const BondPageView: FC<BondPageViewProps> = (props) => {
|
||||
</Text>
|
||||
|
||||
<Group>
|
||||
<Badge color="gray" variant="light">
|
||||
{t(`stock.trading.${vm.quote?.tradingStatus}`, {
|
||||
defaultValue: t("stock.trading.unknown"),
|
||||
})}
|
||||
</Badge>
|
||||
|
||||
{!!vm.quote?.priceAt && (
|
||||
<Text c="dimmed" size="xs">
|
||||
{t("stock.priceAt", {
|
||||
date: formatSnapshotTime(vm.quote.priceAt),
|
||||
})}
|
||||
</Text>
|
||||
)}
|
||||
<InstrumentQuoteStatus quote={vm.quote} />
|
||||
</Group>
|
||||
|
||||
{!!vm.quote?.unavailable.length && (
|
||||
@@ -143,7 +131,7 @@ const BondPageView: FC<BondPageViewProps> = (props) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack className={classes.page} gap="lg">
|
||||
<Stack className={instrumentDetailsStyles.page} gap="lg">
|
||||
<InstrumentHeader
|
||||
logoSrc={vm.logoSrc}
|
||||
name={bond.name}
|
||||
@@ -180,29 +168,29 @@ const BondPageView: FC<BondPageViewProps> = (props) => {
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<div className={classes.embeddedSection}>
|
||||
<div className={instrumentDetailsStyles.embeddedSection}>
|
||||
<Title mb="md" order={2} size="h3">
|
||||
{t("bond.terms")}
|
||||
</Title>
|
||||
|
||||
<div className={classes.companyMetrics}>
|
||||
<Metric
|
||||
<div className={instrumentDetailsStyles.metricsGrid}>
|
||||
<InstrumentMetric
|
||||
label={t("bond.nominal")}
|
||||
value={formatMoney(bond.nominal)}
|
||||
/>
|
||||
|
||||
<Metric
|
||||
<InstrumentMetric
|
||||
label={t("bond.initialNominal")}
|
||||
value={formatMoney(bond.initialNominal)}
|
||||
/>
|
||||
|
||||
<Metric label={t("bond.maturity")} value={maturityDate} />
|
||||
<InstrumentMetric label={t("bond.maturity")} value={maturityDate} />
|
||||
|
||||
<Metric label={t("bond.callDate")} value={callDate} />
|
||||
<InstrumentMetric label={t("bond.callDate")} value={callDate} />
|
||||
|
||||
<Metric label={t("bond.couponFrequency")} value={couponFrequency} />
|
||||
<InstrumentMetric label={t("bond.couponFrequency")} value={couponFrequency} />
|
||||
|
||||
<Metric label={t("bond.lot")} value={lotSize} />
|
||||
<InstrumentMetric label={t("bond.lot")} value={lotSize} />
|
||||
</div>
|
||||
</div>
|
||||
</InstrumentHeader>
|
||||
|
||||
@@ -5,11 +5,9 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import type { BondCoupons as Coupons } from "@/entities/bond";
|
||||
|
||||
import { InstrumentSection, type SectionState } from "@/features/instrument-details";
|
||||
import { InstrumentMetric, InstrumentSection, type SectionState } from "@/features/instrument-details";
|
||||
import { formatDate, formatMoney, formatSnapshotTime } from "@/shared/lib";
|
||||
|
||||
import { Metric } from "./Metric.view";
|
||||
|
||||
type BondCouponsProps = {
|
||||
coupons?: Coupons;
|
||||
pastCoupons: Coupons["coupons"];
|
||||
@@ -116,10 +114,10 @@ const Coupon: FC<CouponProps> = (props) => {
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<Metric label={t("bond.perBond")} value={formatMoney(coupon.perBond)} />
|
||||
<InstrumentMetric label={t("bond.perBond")} value={formatMoney(coupon.perBond)} />
|
||||
|
||||
{!!coupon.estimatedAmount && (
|
||||
<Metric
|
||||
<InstrumentMetric
|
||||
label={t("stock.yourEstimate")}
|
||||
value={formatMoney(coupon.estimatedAmount)}
|
||||
/>
|
||||
|
||||
@@ -5,12 +5,9 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import type { BondMetrics as Metrics } from "@/entities/bond";
|
||||
|
||||
import { InstrumentSection, type SectionState } from "@/features/instrument-details";
|
||||
import { instrumentDetailsStyles, InstrumentMetric, InstrumentSection, type SectionState } from "@/features/instrument-details";
|
||||
import { formatDate, formatDecimal, formatMoney, formatSnapshotTime } from "@/shared/lib";
|
||||
|
||||
import classes from "../BondPage.module.css";
|
||||
import { Metric } from "./Metric.view";
|
||||
|
||||
type BondMetricsProps = { metrics?: Metrics; state: SectionState };
|
||||
|
||||
const BondMetrics: FC<BondMetricsProps> = (props) => {
|
||||
@@ -32,17 +29,17 @@ const BondMetrics: FC<BondMetricsProps> = (props) => {
|
||||
}
|
||||
|
||||
const couponDate = metrics?.couponDate
|
||||
? formatDate(metrics?.couponDate)
|
||||
? formatDate(metrics.couponDate)
|
||||
: t("bond.unknown");
|
||||
|
||||
const effectiveYield = metrics?.effectiveYield
|
||||
? `${formatDecimal(metrics?.effectiveYield.value)} %`
|
||||
? `${formatDecimal(metrics.effectiveYield.value)} %`
|
||||
: t("bond.unknown");
|
||||
|
||||
const couponYield
|
||||
= metrics?.couponYieldPercent == undefined
|
||||
? t("bond.unknown")
|
||||
: `${formatDecimal(metrics?.couponYieldPercent)} %`;
|
||||
: `${formatDecimal(metrics.couponYieldPercent)} %`;
|
||||
|
||||
let yieldContent;
|
||||
|
||||
@@ -50,13 +47,13 @@ const BondMetrics: FC<BondMetricsProps> = (props) => {
|
||||
yieldContent = (
|
||||
<Text c="dimmed" size="xs">
|
||||
{t("bond.yieldSource", {
|
||||
at: formatSnapshotTime(metrics?.effectiveYield.tradeAt),
|
||||
date: formatDate(metrics?.effectiveYield.date),
|
||||
at: formatSnapshotTime(metrics.effectiveYield.tradeAt),
|
||||
date: formatDate(metrics.effectiveYield.date),
|
||||
price: formatDecimal(
|
||||
metrics?.effectiveYield.pricePercent,
|
||||
metrics.effectiveYield.pricePercent,
|
||||
),
|
||||
target: t(
|
||||
`bond.yieldTargets.${metrics?.effectiveYield.dateType}`,
|
||||
`bond.yieldTargets.${metrics.effectiveYield.dateType}`,
|
||||
{ defaultValue: t("bond.yieldTargets.unknown") },
|
||||
),
|
||||
})}
|
||||
@@ -75,26 +72,26 @@ const BondMetrics: FC<BondMetricsProps> = (props) => {
|
||||
<InstrumentSection state={state} title={t("bond.metrics")}>
|
||||
{!!metrics && (
|
||||
<Stack gap="md">
|
||||
<div className={classes.companyMetrics}>
|
||||
<Metric label={t("bond.creditRating")} value={rating} />
|
||||
<div className={instrumentDetailsStyles.metricsGrid}>
|
||||
<InstrumentMetric label={t("bond.creditRating")} value={rating} />
|
||||
|
||||
<Metric
|
||||
<InstrumentMetric
|
||||
label={t("bond.couponAmount")}
|
||||
value={formatMoney(metrics.coupon)}
|
||||
/>
|
||||
|
||||
<Metric
|
||||
<InstrumentMetric
|
||||
label={t("bond.couponType")}
|
||||
value={t(`bond.couponTypes.${metrics.couponType}`, {
|
||||
defaultValue: t("bond.unknown"),
|
||||
})}
|
||||
/>
|
||||
|
||||
<Metric label={t("bond.couponDate")} value={couponDate} />
|
||||
<InstrumentMetric label={t("bond.couponDate")} value={couponDate} />
|
||||
|
||||
<Metric label={t("bond.effectiveYield")} value={effectiveYield} />
|
||||
<InstrumentMetric label={t("bond.effectiveYield")} value={effectiveYield} />
|
||||
|
||||
<Metric label={t("bond.couponYield")} value={couponYield} />
|
||||
<InstrumentMetric label={t("bond.couponYield")} value={couponYield} />
|
||||
</div>
|
||||
|
||||
{!!metrics.creditRating && (
|
||||
@@ -128,10 +125,10 @@ const BondMetrics: FC<BondMetricsProps> = (props) => {
|
||||
{t("bond.couponYieldNote")}
|
||||
</Text>
|
||||
|
||||
{!!metrics?.couponYieldPriceAt && (
|
||||
{!!metrics.couponYieldPriceAt && (
|
||||
<Text c="dimmed" size="xs">
|
||||
{t("bond.couponYieldPriceAt", {
|
||||
date: formatSnapshotTime(metrics?.couponYieldPriceAt),
|
||||
date: formatSnapshotTime(metrics.couponYieldPriceAt),
|
||||
})}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import type { FC } from "react";
|
||||
|
||||
import { Text } from "@mantine/core";
|
||||
|
||||
type MetricProps = { label: string; value: string };
|
||||
|
||||
const Metric: FC<MetricProps> = props => (
|
||||
<div>
|
||||
<Text c="dimmed" size="xs">
|
||||
{props.label}
|
||||
</Text>
|
||||
|
||||
<Text fw={600} size="lg">
|
||||
{props.value}
|
||||
</Text>
|
||||
</div>
|
||||
);
|
||||
|
||||
export { Metric };
|
||||
Reference in New Issue
Block a user