117 lines
2.9 KiB
TypeScript
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 };
|