Big refactoring
Some checks failed
Deploy / lint (push) Failing after 21s
Deploy / test (push) Has been skipped
Deploy / deploy (push) Has been skipped

This commit is contained in:
Dennis Thiessen
2026-03-03 15:20:18 +01:00
parent 181cfe6588
commit 0a011d4ce9
55 changed files with 6898 additions and 544 deletions

View File

@@ -1,6 +1,7 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import * as adminApi from '../api/admin';
import { useToast } from '../components/ui/Toast';
import type { TickerUniverse } from '../lib/types';
// ── Users ──
@@ -89,13 +90,93 @@ export function useUpdateSetting() {
});
}
export function useRecommendationSettings() {
return useQuery({
queryKey: ['admin', 'recommendation-settings'],
queryFn: () => adminApi.getRecommendationSettings(),
});
}
export function useUpdateRecommendationSettings() {
const qc = useQueryClient();
const { addToast } = useToast();
return useMutation({
mutationFn: (payload: Record<string, number>) =>
adminApi.updateRecommendationSettings(payload),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['admin', 'recommendation-settings'] });
addToast('success', 'Recommendation settings updated');
},
onError: (error: Error) => {
addToast('error', error.message || 'Failed to update recommendation settings');
},
});
}
export function useTickerUniverseSetting() {
return useQuery({
queryKey: ['admin', 'ticker-universe'],
queryFn: () => adminApi.getTickerUniverseSetting(),
});
}
export function useUpdateTickerUniverseSetting() {
const qc = useQueryClient();
const { addToast } = useToast();
return useMutation({
mutationFn: (universe: TickerUniverse) => adminApi.updateTickerUniverseSetting(universe),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['admin', 'ticker-universe'] });
addToast('success', 'Default ticker universe updated');
},
onError: (error: Error) => {
addToast('error', error.message || 'Failed to update default ticker universe');
},
});
}
export function useBootstrapTickers() {
const qc = useQueryClient();
const { addToast } = useToast();
return useMutation({
mutationFn: ({ universe, pruneMissing }: { universe: TickerUniverse; pruneMissing: boolean }) =>
adminApi.bootstrapTickers(universe, pruneMissing),
onSuccess: (result) => {
qc.invalidateQueries({ queryKey: ['tickers'] });
qc.invalidateQueries({ queryKey: ['admin', 'ticker-universe'] });
addToast(
'success',
`Bootstrap done: +${result.added}, existing ${result.already_tracked}, deleted ${result.deleted}`,
);
},
onError: (error: Error) => {
addToast('error', error.message || 'Failed to bootstrap tickers');
},
});
}
// ── Jobs ──
export function useJobs() {
return useQuery({
queryKey: ['admin', 'jobs'],
queryFn: () => adminApi.listJobs(),
refetchInterval: 15_000,
refetchInterval: (query) => {
const jobs = (query.state.data ?? []) as adminApi.JobStatus[];
const hasRunning = jobs.some((job) => job.running);
return hasRunning ? 2_000 : 15_000;
},
});
}
export function usePipelineReadiness() {
return useQuery({
queryKey: ['admin', 'pipeline-readiness'],
queryFn: () => adminApi.getPipelineReadiness(),
refetchInterval: 20_000,
});
}
@@ -121,9 +202,13 @@ export function useTriggerJob() {
return useMutation({
mutationFn: (jobName: string) => adminApi.triggerJob(jobName),
onSuccess: () => {
onSuccess: (result) => {
qc.invalidateQueries({ queryKey: ['admin', 'jobs'] });
addToast('success', 'Job triggered successfully');
if (result.status === 'triggered') {
addToast('success', result.message || 'Job triggered successfully');
return;
}
addToast('info', result.message || 'Job could not be triggered');
},
onError: (error: Error) => {
addToast('error', error.message || 'Failed to trigger job');

View File

@@ -0,0 +1,42 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { fetchData, type FetchDataResult } from '../api/ingestion';
import { useToast } from '../components/ui/Toast';
import { summarizeIngestionResult } from '../lib/ingestionStatus';
interface UseFetchSymbolDataOptions {
includeSymbolPrefix?: boolean;
invalidatePipelineReadiness?: boolean;
}
export function useFetchSymbolData(options: UseFetchSymbolDataOptions = {}) {
const { includeSymbolPrefix = false, invalidatePipelineReadiness = false } = options;
const queryClient = useQueryClient();
const { addToast } = useToast();
return useMutation({
mutationFn: (symbol: string) => fetchData(symbol),
onSuccess: (result: FetchDataResult, symbol: string) => {
const normalized = symbol.toUpperCase();
const summary = summarizeIngestionResult(result, normalized);
const toastMessage = includeSymbolPrefix
? `${normalized}: ${summary.message}`
: summary.message;
addToast(summary.toastType, toastMessage);
queryClient.invalidateQueries({ queryKey: ['ohlcv', symbol] });
queryClient.invalidateQueries({ queryKey: ['sentiment', symbol] });
queryClient.invalidateQueries({ queryKey: ['fundamentals', symbol] });
queryClient.invalidateQueries({ queryKey: ['sr-levels', symbol] });
queryClient.invalidateQueries({ queryKey: ['scores', symbol] });
if (invalidatePipelineReadiness) {
queryClient.invalidateQueries({ queryKey: ['admin', 'pipeline-readiness'] });
}
},
onError: (err: Error, symbol: string) => {
const normalized = symbol.toUpperCase();
const prefix = includeSymbolPrefix ? `${normalized}: ` : '';
addToast('error', `${prefix}${err.message || 'Failed to fetch data'}`);
},
});
}

View File

@@ -38,8 +38,8 @@ export function useTickerDetail(symbol: string) {
});
const trades = useQuery({
queryKey: ['trades'],
queryFn: () => tradesApi.list(),
queryKey: ['trades', symbol],
queryFn: () => tradesApi.bySymbol(symbol),
enabled: !!symbol,
});