refactor: encapsulate instrument chart data
Frontend verify / commits (pull_request) Successful in 27s
Frontend verify / security (pull_request) Successful in 10s
Frontend verify / verify (pull_request) Successful in 8m45s

This commit is contained in:
2026-09-28 20:46:06 +03:00
parent fb3a5a6eaa
commit 64fe902192
7 changed files with 50 additions and 65 deletions
@@ -7,17 +7,13 @@ type Candles = NonNullable<
paths["/api/v1/stocks/{uid}/candles"]["get"]["responses"][200]["content"]["application/json"]["data"]
>;
type ChartRow = Candles["candles"][number] & { date: string; price: number };
type InstrumentChartProps = { priceUnit?: "percent"; vm: InstrumentChartVm };
type InstrumentChartVm = {
averageLine: null | number;
candles?: Candles;
candlesState: SectionState;
chartData: ChartRow[];
hasIncompleteCandle: boolean;
period: string;
position?: Position;
priceRows: ChartRow[];
setPeriod: (value: string) => void;
};
@@ -41,4 +37,4 @@ type Position = NonNullable<
paths["/api/v1/stocks/{uid}/position"]["get"]["responses"][200]["content"]["application/json"]["data"]
>;
export type { InstrumentChartVm, InstrumentEventsVm, InstrumentPositionVm };
export type { InstrumentChartProps, InstrumentEventsVm, InstrumentPositionVm };
@@ -10,20 +10,19 @@ import {
Text,
} from "@mantine/core";
import { formatMoney } from "@/shared/lib";
import { formatChartValue, formatMoney } from "@/shared/lib";
import type { InstrumentChartVm } from "../../model/types";
import type { InstrumentChartProps } from "../../model/types";
import classes from "../InstrumentDetails.module.css";
import { InstrumentSection } from "../InstrumentSection.view";
import { useInstrumentChartVm } from "./InstrumentChart.vm";
type InstrumentChartProps = { priceUnit?: "percent"; vm: InstrumentChartVm };
const InstrumentChart: FC<InstrumentChartProps> = (props) => {
const {
chartData,
chartLabel,
formatChartValue,
hasIncompleteCandle,
periodOptions,
priceRows,
referenceLines,
@@ -34,14 +33,14 @@ const InstrumentChart: FC<InstrumentChartProps> = (props) => {
let chartContent;
if (vm.candles) {
if (vm.chartData.length > 0) {
if (chartData.length > 0) {
chartContent = (
<>
<AreaChart
areaProps={{ isAnimationActive: false }}
aria-label={chartLabel}
curveType="linear"
data={vm.chartData}
data={chartData}
dataKey="date"
gridAxis="y"
h={310}
@@ -52,7 +51,7 @@ const InstrumentChart: FC<InstrumentChartProps> = (props) => {
tickLine="none"
tooltipAnimationDuration={0}
valueFormatter={formatChartValue}
withDots={vm.chartData.length === 1}
withDots={chartData.length === 1}
withGradient
yAxisProps={{ domain: ["auto", "auto"], width: 64 }}
/>
@@ -65,13 +64,13 @@ const InstrumentChart: FC<InstrumentChartProps> = (props) => {
)}
</Text>
{!!vm.hasIncompleteCandle && (
{!!hasIncompleteCandle && (
<Text c="dimmed" size="xs">
{t("stock.incomplete")}
</Text>
)}
{vm.averageLine !== null && (
{referenceLines.length > 0 && (
<Text mt="xs" size="sm">
{t("stock.averageLine")}:{" "}
{formatMoney(vm.position?.averagePurchasePrice)}
@@ -88,7 +87,7 @@ const InstrumentChart: FC<InstrumentChartProps> = (props) => {
<Text fw={600}>{t("stock.priceTable")}</Text>
<Text c="dimmed" size="xs">
{t("stock.priceTableHint", { count: vm.chartData.length })}
{t("stock.priceTableHint", { count: chartData.length })}
</Text>
</Accordion.Control>
@@ -1,16 +1,27 @@
import { useTranslation } from "react-i18next";
import { formatChartValue, formatDecimal, formatMoney } from "@/shared/lib";
import { formatDate, formatDecimal, formatMoney } from "@/shared/lib";
import type { InstrumentChartVm } from "../../model/types";
type InstrumentChartProps = { priceUnit?: "percent"; vm: InstrumentChartVm };
import type { InstrumentChartProps } from "../../model/types";
function useInstrumentChartVm(props: InstrumentChartProps) {
const { vm } = props;
const { t } = useTranslation();
const priceRows = vm.priceRows.map((candle) => {
const chartData = vm.candles?.candles.map(candle => ({
...candle,
date: formatDate(candle.time),
price: Number(candle.close),
})) ?? [];
const average = vm.position?.averagePurchasePrice;
const averageLine = average && props.priceUnit !== "percent"
&& average.currency === vm.candles?.currency
? Number(average.value)
: null;
const priceRows = chartData.toReversed().map((candle) => {
const currency = vm.candles?.currency;
const value = candle.close;
@@ -39,20 +50,21 @@ function useInstrumentChartVm(props: InstrumentChartProps) {
);
const referenceLines
= vm.averageLine === null
= averageLine === null || !Number.isFinite(averageLine)
? []
: [
{
color: "orange.7",
ifOverflow: "extendDomain" as const,
label: t("stock.averageLine"),
y: vm.averageLine,
y: averageLine,
},
];
return {
chartData,
chartLabel,
formatChartValue,
hasIncompleteCandle: chartData.some(candle => !candle.isComplete),
periodOptions,
priceRows,
referenceLines,