Files
frontend/src/pages/stocks/ui/StocksPage/StocksPage.vm.ts
T
2026-10-11 10:44:30 +03:00

117 lines
2.9 KiB
TypeScript

import { useRef, useState } from "react";
import { apiClient } from "@/shared/api";
type Criteria = {
countryOfRisk: string;
currency: string;
maxLot: string;
query: string;
realExchange: string;
sector: string;
shareType: string;
};
type ScreenerPage = Awaited<ReturnType<typeof readPage>>;
type StocksPageVm = ReturnType<typeof useStocksPageVm>;
const emptyCriteria: Criteria = {
countryOfRisk: "",
currency: "",
maxLot: "",
query: "",
realExchange: "",
sector: "",
shareType: "",
};
async function createSearch(criteria: Criteria) {
const { data } = await apiClient.POST("/api/v1/stocks/screener/search", {
body: {
...(criteria.countryOfRisk && { countryOfRisk: criteria.countryOfRisk }),
...(criteria.currency && { currency: criteria.currency }),
...(criteria.maxLot && { maxLot: Number(criteria.maxLot) }),
...(criteria.query && { query: criteria.query }),
...(criteria.realExchange && { realExchange: criteria.realExchange }),
...(criteria.sector && { sector: criteria.sector }),
...(criteria.shareType && { shareType: criteria.shareType }),
},
});
if (!data) throw new Error("Stock screener search failed");
return data;
}
async function readPage(searchId: string, page: number) {
const { data } = await apiClient.GET(
"/api/v1/stocks/screener/search/{searchId}",
{ params: { path: { searchId }, query: { page } } },
);
if (!data) throw new Error("Stock screener page failed");
return data;
}
function useStocksPageVm() {
const [criteria, setCriteria] = useState(emptyCriteria);
const [page, setPage] = useState<null | ScreenerPage>(null);
const [pending, setPending] = useState(false);
const [error, setError] = useState(false);
const requestIdRef = useRef(0);
const search = async () => {
const currentRequestId = requestIdRef.current + 1;
requestIdRef.current = currentRequestId;
setPending(true);
setError(false);
try {
const result = await createSearch(criteria);
if (requestIdRef.current === currentRequestId) setPage(result);
}
catch {
if (requestIdRef.current === currentRequestId) setError(true);
}
finally {
if (requestIdRef.current === currentRequestId) setPending(false);
}
};
const changePage = async (nextPage: number) => {
if (!page) return;
setPending(true);
setError(false);
try {
setPage(await readPage(page.searchId, nextPage));
}
catch {
setError(true);
}
finally {
setPending(false);
}
};
return {
criteria,
error,
items: page?.items ?? [],
loading: pending,
onCriteriaChange: (name: keyof Criteria, value: string) =>
setCriteria(current => ({ ...current, [name]: value })),
onPage: (nextPage: number) => void changePage(nextPage),
onSearch: () => void search(),
page: page?.page,
resultTime: page?.asOf,
searched: page !== null,
totalPages: page ? page.page + Number(page.hasNext) : 0,
};
}
export { type Criteria, type StocksPageVm, useStocksPageVm };