mirror of
https://github.com/Zproger/Aurum.git
synced 2026-10-13 02:56:39 +07:00
График во вкладке «Криптовалюта» теперь по умолчанию показывает всю историю, а не 30 дней — короткое окно у портфеля, растущего годами, часто попадало на локальную просадку и выглядело демотивирующе. Плитки «Лучший рост» и «Худшее падение» раньше всегда считались за 24 часа. Теперь они следуют за диапазоном, выбранным у графика (7д/30д/90д/всё время): для 7д/30д используются закэшированные данные CoinGecko, для «всё время» — метрика за 1 год (дальше бесплатный тариф CoinGecko историю не отдаёт), а для 90д — отдельный запрос по каждой монете, который выполняется только когда пользователь реально выбрал этот период.
146 lines
4.9 KiB
TypeScript
146 lines
4.9 KiB
TypeScript
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
import {
|
|
addCryptoTransaction,
|
|
createCryptoHolding,
|
|
createCryptoPortfolio,
|
|
deleteCryptoHolding,
|
|
deleteCryptoPortfolio,
|
|
deleteCryptoTransaction,
|
|
fetchCrypto90dPerformance,
|
|
fetchCryptoHistory,
|
|
fetchCryptoHoldings,
|
|
fetchCryptoPortfolios,
|
|
fetchCryptoTransactions,
|
|
refreshCryptoPrices,
|
|
updateCryptoPortfolio,
|
|
updateCryptoTransaction,
|
|
} from "@/api/crypto";
|
|
import type { CryptoHoldingCreateInput, CryptoPortfolioInput, CryptoRange, CryptoTransactionInput } from "@/types";
|
|
|
|
function useInvalidateCrypto() {
|
|
const queryClient = useQueryClient();
|
|
return () => {
|
|
queryClient.invalidateQueries({ queryKey: ["crypto-holdings"] });
|
|
queryClient.invalidateQueries({ queryKey: ["crypto-transactions"] });
|
|
queryClient.invalidateQueries({ queryKey: ["crypto-history"] });
|
|
// A crypto holding is a normal Asset under the hood — Net Worth's own
|
|
// numbers change the moment one is added/traded/repriced/removed.
|
|
queryClient.invalidateQueries({ queryKey: ["net-worth-summary"] });
|
|
};
|
|
}
|
|
|
|
export function useCryptoPortfolios(includeArchived = false) {
|
|
return useQuery({
|
|
queryKey: ["crypto-portfolios", includeArchived],
|
|
queryFn: () => fetchCryptoPortfolios(includeArchived),
|
|
});
|
|
}
|
|
|
|
export function useCreateCryptoPortfolio() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (input: CryptoPortfolioInput) => createCryptoPortfolio(input),
|
|
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["crypto-portfolios"] }),
|
|
});
|
|
}
|
|
|
|
export function useUpdateCryptoPortfolio() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ id, input }: { id: number; input: Partial<CryptoPortfolioInput> }) =>
|
|
updateCryptoPortfolio(id, input),
|
|
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["crypto-portfolios"] }),
|
|
});
|
|
}
|
|
|
|
export function useDeleteCryptoPortfolio() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (id: number) => deleteCryptoPortfolio(id),
|
|
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["crypto-portfolios"] }),
|
|
});
|
|
}
|
|
|
|
export function useCryptoHoldings(portfolioId?: number | null) {
|
|
return useQuery({
|
|
queryKey: ["crypto-holdings", portfolioId ?? null],
|
|
queryFn: () => fetchCryptoHoldings(portfolioId),
|
|
});
|
|
}
|
|
|
|
export function useCryptoHistory(range: CryptoRange, portfolioId?: number | null) {
|
|
return useQuery({
|
|
queryKey: ["crypto-history", range, portfolioId ?? null],
|
|
queryFn: () => fetchCryptoHistory(range, portfolioId),
|
|
});
|
|
}
|
|
|
|
// Only fires while the 90d range is actually selected — a real CoinGecko
|
|
// call per held coin (see get_90d_performance's docstring), not something
|
|
// to run on every Crypto tab visit the way 7d/30d/1y already do for free.
|
|
export function useCrypto90dPerformance(range: CryptoRange, portfolioId?: number | null) {
|
|
return useQuery({
|
|
queryKey: ["crypto-performance-90d", portfolioId ?? null],
|
|
queryFn: () => fetchCrypto90dPerformance(portfolioId),
|
|
enabled: range === "90d",
|
|
});
|
|
}
|
|
|
|
export function useCryptoTransactions(assetId: number | null) {
|
|
return useQuery({
|
|
queryKey: ["crypto-transactions", assetId],
|
|
queryFn: () => fetchCryptoTransactions(assetId!),
|
|
enabled: assetId !== null,
|
|
});
|
|
}
|
|
|
|
export function useRefreshCryptoPrices() {
|
|
const invalidate = useInvalidateCrypto();
|
|
// Always a bare call — sync covers every portfolio regardless of which
|
|
// tab is active (see refresh_prices' own docstring), so there's no
|
|
// portfolio_id variable for callers to pass here.
|
|
return useMutation({ mutationFn: () => refreshCryptoPrices(), onSuccess: invalidate });
|
|
}
|
|
|
|
export function useCreateCryptoHolding() {
|
|
const invalidate = useInvalidateCrypto();
|
|
return useMutation({
|
|
mutationFn: (input: CryptoHoldingCreateInput) => createCryptoHolding(input),
|
|
onSuccess: invalidate,
|
|
});
|
|
}
|
|
|
|
export function useAddCryptoTransaction() {
|
|
const invalidate = useInvalidateCrypto();
|
|
return useMutation({
|
|
mutationFn: ({ assetId, input }: { assetId: number; input: CryptoTransactionInput }) =>
|
|
addCryptoTransaction(assetId, input),
|
|
onSuccess: invalidate,
|
|
});
|
|
}
|
|
|
|
export function useUpdateCryptoTransaction() {
|
|
const invalidate = useInvalidateCrypto();
|
|
return useMutation({
|
|
mutationFn: ({ transactionId, input }: { transactionId: number; input: CryptoTransactionInput }) =>
|
|
updateCryptoTransaction(transactionId, input),
|
|
onSuccess: invalidate,
|
|
});
|
|
}
|
|
|
|
export function useDeleteCryptoTransaction() {
|
|
const invalidate = useInvalidateCrypto();
|
|
return useMutation({
|
|
mutationFn: (transactionId: number) => deleteCryptoTransaction(transactionId),
|
|
onSuccess: invalidate,
|
|
});
|
|
}
|
|
|
|
export function useDeleteCryptoHolding() {
|
|
const invalidate = useInvalidateCrypto();
|
|
return useMutation({
|
|
mutationFn: (assetId: number) => deleteCryptoHolding(assetId),
|
|
onSuccess: invalidate,
|
|
});
|
|
}
|