refactor: tighten types and reuse instrument UI
This commit is contained in:
@@ -1,39 +1,11 @@
|
||||
.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;
|
||||
}
|
||||
.positive {
|
||||
color: var(--mantine-color-green-8);
|
||||
color: var(--wl-color-positive);
|
||||
}
|
||||
.negative {
|
||||
color: var(--mantine-color-red-8);
|
||||
color: var(--wl-color-negative);
|
||||
}
|
||||
.dividend {
|
||||
padding: var(--mantine-spacing-md);
|
||||
background: var(--mantine-color-green-0);
|
||||
border-radius: var(--mantine-radius-md);
|
||||
}
|
||||
.companyMetrics {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(135px, 1fr));
|
||||
gap: var(--mantine-spacing-lg);
|
||||
}
|
||||
.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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { FC } from "react";
|
||||
|
||||
import {
|
||||
Alert,
|
||||
Badge,
|
||||
Button,
|
||||
Group,
|
||||
Skeleton,
|
||||
@@ -13,7 +12,10 @@ import { LoadingSkeleton } from "@wealthledger/ui";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import {
|
||||
instrumentDetailsStyles,
|
||||
InstrumentHeader,
|
||||
InstrumentMetric,
|
||||
InstrumentQuoteStatus,
|
||||
InstrumentChart as StockChart,
|
||||
InstrumentEvents as StockEvents,
|
||||
InstrumentPosition as StockPosition,
|
||||
@@ -23,7 +25,6 @@ import {
|
||||
formatDate,
|
||||
formatDecimal,
|
||||
formatMoney,
|
||||
formatSnapshotTime,
|
||||
} from "@/shared/lib";
|
||||
|
||||
import type { StockPageVm } from "./StockPage.vm";
|
||||
@@ -31,6 +32,16 @@ import type { StockPageVm } from "./StockPage.vm";
|
||||
import { StockDividends } from "./components/StockDividends.view";
|
||||
import classes from "./StockPage.module.css";
|
||||
|
||||
const compactMetricFormatter = new Intl.NumberFormat("ru-RU", {
|
||||
maximumFractionDigits: 2,
|
||||
notation: "compact",
|
||||
});
|
||||
|
||||
const metricFormatter = new Intl.NumberFormat("ru-RU", {
|
||||
maximumFractionDigits: 2,
|
||||
notation: "standard",
|
||||
});
|
||||
|
||||
type StockPageViewProps = { vm: StockPageVm };
|
||||
|
||||
const StockPageView: FC<StockPageViewProps> = (props) => {
|
||||
@@ -64,13 +75,13 @@ const StockPageView: FC<StockPageViewProps> = (props) => {
|
||||
else {
|
||||
quoteContent = (
|
||||
<div aria-live="polite">
|
||||
<Text className={classes.price} mt="lg">
|
||||
<Text className={instrumentDetailsStyles.price} mt="lg">
|
||||
{formatMoney(vm.quote?.price)}
|
||||
</Text>
|
||||
|
||||
{!!vm.quote?.change && (
|
||||
<Text className={appearanceClass} fw={600}>
|
||||
{vm.quote?.change?.value.startsWith("-") ? "" : "+"}
|
||||
{vm.quote.change.value.startsWith("-") ? "" : "+"}
|
||||
|
||||
{formatMoney(vm.quote.change)}
|
||||
|
||||
@@ -95,19 +106,7 @@ const StockPageView: FC<StockPageViewProps> = (props) => {
|
||||
)}
|
||||
|
||||
<Group gap="sm" mt="sm">
|
||||
<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 && (
|
||||
@@ -130,25 +129,16 @@ const StockPageView: FC<StockPageViewProps> = (props) => {
|
||||
if (vm.company) {
|
||||
if (vm.company.metrics.length > 0) {
|
||||
companyMetricsContent = (
|
||||
<div className={classes.companyMetrics}>
|
||||
{vm.company.metrics.map(metric => (
|
||||
<div key={metric.key}>
|
||||
<Text c="dimmed" size="xs">
|
||||
{t(`stock.metrics.${metric.key}`)}
|
||||
<div className={instrumentDetailsStyles.metricsGrid}>
|
||||
{vm.company.metrics.map((metric) => {
|
||||
const label = [t(`stock.metrics.${metric.key}`), metric.period].filter(Boolean).join(" · ");
|
||||
const formatter = metric.unit === "money" ? compactMetricFormatter : metricFormatter;
|
||||
const value = `${formatter.format(metric.value)} ${metricUnit(metric, t("stock.currencyUnknown"))}`;
|
||||
|
||||
{metric.period ? ` · ${metric.period}` : ""}
|
||||
</Text>
|
||||
|
||||
<Text fw={600} size="lg">
|
||||
{new Intl.NumberFormat("ru-RU", {
|
||||
maximumFractionDigits: 2,
|
||||
notation:
|
||||
metric.unit === "money" ? "compact" : "standard",
|
||||
}).format(metric.value)}{" "}
|
||||
{metricUnit(metric, t("stock.currencyUnknown"))}
|
||||
</Text>
|
||||
</div>
|
||||
))}
|
||||
return (
|
||||
<InstrumentMetric key={metric.key} label={label} value={value} />
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -158,7 +148,7 @@ const StockPageView: FC<StockPageViewProps> = (props) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack className={classes.page} gap="lg">
|
||||
<Stack className={instrumentDetailsStyles.page} gap="lg">
|
||||
<InstrumentHeader
|
||||
logoSrc={vm.logoSrc}
|
||||
name={stock.name}
|
||||
|
||||
Reference in New Issue
Block a user