147 lines
4.5 KiB
TypeScript
147 lines
4.5 KiB
TypeScript
import { useQueryClient } from "@tanstack/react-query";
|
|
import { useState } from "react";
|
|
import { useParams, useSearchParams } from "react-router";
|
|
import {
|
|
type BondPeriod,
|
|
useBond,
|
|
useBondCandles,
|
|
useBondCoupons,
|
|
useBondMetrics,
|
|
useBondPosition,
|
|
useBondQuote,
|
|
} from "@/entities/bond";
|
|
import {
|
|
brokerageOperationsQueryOptions,
|
|
type OperationCategory,
|
|
useBrokerageOperations,
|
|
} from "@/entities/broker-operation";
|
|
import { section } from "@/features/instrument-details";
|
|
import { apiErrorCode } from "@/shared/api";
|
|
import { formatDate, instrumentLogoUrl } from "@/shared/lib";
|
|
|
|
const periods: BondPeriod[] = ["month", "halfYear", "year", "all"];
|
|
const filters = ["all", "trades", "coupons", "other"] as const;
|
|
type BondEventFilter = (typeof filters)[number];
|
|
const categories: Record<BondEventFilter, OperationCategory[]> = {
|
|
all: [],
|
|
trades: ["trade"],
|
|
coupons: ["income"],
|
|
other: ["tax", "fee", "transfer", "other"],
|
|
};
|
|
function useBondPageVm() {
|
|
const { bondId = "" } = useParams();
|
|
const bond = useBond(bondId);
|
|
const uid = bond.data?.instrumentUid ?? "";
|
|
const [params, setParams] = useSearchParams();
|
|
const period =
|
|
periods.find((value) => value === params.get("period")) ?? "month";
|
|
const filter =
|
|
filters.find((value) => value === params.get("events")) ?? "all";
|
|
const scope = `${uid}:${filter}`;
|
|
const [pagination, setPagination] = useState<{
|
|
scope: string;
|
|
cursors: string[];
|
|
}>({ scope, cursors: [] });
|
|
const cursors = pagination.scope === scope ? pagination.cursors : [];
|
|
const client = useQueryClient();
|
|
const quote = useBondQuote(uid);
|
|
const metrics = useBondMetrics(uid);
|
|
const candles = useBondCandles(uid, period);
|
|
const position = useBondPosition(uid);
|
|
const coupons = useBondCoupons(uid);
|
|
const firstPage = {
|
|
instrumentUid: uid,
|
|
categories: categories[filter],
|
|
limit: 20,
|
|
};
|
|
const operations = useBrokerageOperations(uid ? "all" : undefined, {
|
|
...firstPage,
|
|
cursor: cursors.at(-1),
|
|
});
|
|
const setParam = (key: string, value: string) => {
|
|
const next = new URLSearchParams(params);
|
|
next.set(key, value);
|
|
setParams(next, { replace: true });
|
|
};
|
|
const retryOperations = () => {
|
|
if (apiErrorCode(operations.error) === "INVALID_OPERATIONS_CURSOR") {
|
|
setPagination({ scope, cursors: [] });
|
|
void client.invalidateQueries({
|
|
queryKey: brokerageOperationsQueryOptions("all", firstPage).queryKey,
|
|
exact: true,
|
|
});
|
|
} else void operations.refetch();
|
|
};
|
|
const chartData =
|
|
candles.data?.candles.map((candle) => ({
|
|
...candle,
|
|
date: formatDate(candle.time),
|
|
price: Number(candle.close),
|
|
})) ?? [];
|
|
return {
|
|
canonicalTo:
|
|
bond.data?.slug && bond.data.slug !== bondId
|
|
? `/bonds/${encodeURIComponent(bond.data.slug)}${params.size ? `?${params.toString()}` : ""}`
|
|
: null,
|
|
bond: bond.data,
|
|
bondState: section(bond),
|
|
code: apiErrorCode(bond.error),
|
|
logoSrc: instrumentLogoUrl(bond.data?.logoName),
|
|
metrics: metrics.data,
|
|
metricsState: section(metrics),
|
|
quote: quote.data,
|
|
quoteState: section(quote),
|
|
candles: candles.data,
|
|
chartData,
|
|
priceRows: chartData.toReversed(),
|
|
candlesState: section(candles),
|
|
period,
|
|
setPeriod: (value: string) => setParam("period", value),
|
|
averageLine: null,
|
|
hasIncompleteCandle:
|
|
candles.data?.candles.some((candle) => !candle.isComplete) ?? false,
|
|
position: position.data,
|
|
positionState: section(position),
|
|
coupons: coupons.data,
|
|
couponsState: section(coupons),
|
|
upcoming:
|
|
coupons.data?.coupons
|
|
.filter((event) => event.timing === "upcoming")
|
|
.sort((a, b) =>
|
|
(a.paymentDate ?? a.recordDate ?? "").localeCompare(
|
|
b.paymentDate ?? b.recordDate ?? "",
|
|
),
|
|
) ?? [],
|
|
pastCoupons:
|
|
coupons.data?.coupons
|
|
.filter((event) => event.timing !== "upcoming")
|
|
.sort((a, b) =>
|
|
(b.paymentDate ?? b.recordDate ?? "").localeCompare(
|
|
a.paymentDate ?? a.recordDate ?? "",
|
|
),
|
|
) ?? [],
|
|
operations: operations.data,
|
|
operationsState: { ...section(operations), retry: retryOperations },
|
|
eventRows:
|
|
operations.data?.items.map((item) => ({
|
|
...item,
|
|
ticker: item.ticker ?? bond.data?.ticker ?? null,
|
|
logoName: item.logoName ?? bond.data?.logoName ?? null,
|
|
})) ?? [],
|
|
filter,
|
|
setFilter: (value: string) => setParam("events", value),
|
|
hasPrevious: cursors.length > 0,
|
|
onPrevious: () => setPagination({ scope, cursors: cursors.slice(0, -1) }),
|
|
onNext: () => {
|
|
if (operations.data?.nextCursor)
|
|
setPagination({
|
|
scope,
|
|
cursors: [...cursors, operations.data.nextCursor],
|
|
});
|
|
},
|
|
};
|
|
}
|
|
type BondPageVm = ReturnType<typeof useBondPageVm>;
|
|
|
|
export { type BondPageVm, useBondPageVm };
|