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 ── export function useUsers() { return useQuery({ queryKey: ['admin', 'users'], queryFn: () => adminApi.listUsers(), }); } export function useCreateUser() { const qc = useQueryClient(); const { addToast } = useToast(); return useMutation({ mutationFn: (data: { username: string; password: string; role: string; has_access: boolean; }) => adminApi.createUser(data), onSuccess: () => { qc.invalidateQueries({ queryKey: ['admin', 'users'] }); }, onError: (error: Error) => { addToast('error', error.message || 'Failed to create user'); }, }); } export function useUpdateAccess() { const qc = useQueryClient(); const { addToast } = useToast(); return useMutation({ mutationFn: ({ userId, hasAccess }: { userId: number; hasAccess: boolean }) => adminApi.updateAccess(userId, hasAccess), onSuccess: () => { qc.invalidateQueries({ queryKey: ['admin', 'users'] }); }, onError: (error: Error) => { addToast('error', error.message || 'Failed to update access'); }, }); } export function useResetPassword() { const qc = useQueryClient(); const { addToast } = useToast(); return useMutation({ mutationFn: ({ userId, password }: { userId: number; password: string }) => adminApi.resetPassword(userId, password), onSuccess: () => { qc.invalidateQueries({ queryKey: ['admin', 'users'] }); addToast('success', 'Password reset successfully'); }, onError: (error: Error) => { addToast('error', error.message || 'Failed to reset password'); }, }); } // ── Settings ── export function useSettings() { return useQuery({ queryKey: ['admin', 'settings'], queryFn: () => adminApi.listSettings(), }); } export function useUpdateSetting() { const qc = useQueryClient(); const { addToast } = useToast(); return useMutation({ mutationFn: ({ key, value }: { key: string; value: string }) => adminApi.updateSetting(key, value), onSuccess: () => { qc.invalidateQueries({ queryKey: ['admin', 'settings'] }); }, onError: (error: Error) => { addToast('error', error.message || 'Failed to update setting'); }, }); } 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) => 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 useActivationSettings() { return useQuery({ queryKey: ['admin', 'activation-settings'], queryFn: () => adminApi.getActivationSettings(), }); } export function useUpdateActivationSettings() { const qc = useQueryClient(); const { addToast } = useToast(); return useMutation({ mutationFn: (payload: Partial) => adminApi.updateActivationSettings(payload), onSuccess: () => { qc.invalidateQueries({ queryKey: ['admin', 'activation-settings'] }); qc.invalidateQueries({ queryKey: ['activation'] }); qc.invalidateQueries({ queryKey: ['performance'] }); addToast('success', 'Activation gate updated'); }, onError: (error: Error) => { addToast('error', error.message || 'Failed to update activation thresholds'); }, }); } export function useScheduleSettings() { return useQuery({ queryKey: ['admin', 'schedule-settings'], queryFn: () => adminApi.getScheduleSettings(), }); } export function useUpdateScheduleSettings() { const qc = useQueryClient(); const { addToast } = useToast(); return useMutation({ mutationFn: (payload: Partial) => adminApi.updateScheduleSettings(payload), onSuccess: () => { qc.invalidateQueries({ queryKey: ['admin', 'schedule-settings'] }); qc.invalidateQueries({ queryKey: ['admin', 'jobs'] }); addToast('success', 'Schedule updated'); }, onError: (error: Error) => { addToast('error', error.message || 'Failed to update schedule'); }, }); } export function useSentimentSettings() { return useQuery({ queryKey: ['admin', 'sentiment-settings'], queryFn: () => adminApi.getSentimentSettings(), }); } export function useUpdateSentimentSettings() { const qc = useQueryClient(); const { addToast } = useToast(); return useMutation({ mutationFn: (payload: { provider?: string; model?: string; api_key?: string }) => adminApi.updateSentimentSettings(payload), onSuccess: () => { qc.invalidateQueries({ queryKey: ['admin', 'sentiment-settings'] }); addToast('success', 'Sentiment provider updated'); }, onError: (error: Error) => { addToast('error', error.message || 'Failed to update sentiment provider'); }, }); } export function useTestSentimentProvider() { const { addToast } = useToast(); return useMutation({ mutationFn: (ticker: string) => adminApi.testSentimentSettings(ticker), onError: (error: Error) => { addToast('error', error.message || 'Sentiment test failed'); }, }); } export function useAlertSettings() { return useQuery({ queryKey: ['admin', 'alert-settings'], queryFn: () => adminApi.getAlertSettings(), }); } export function useUpdateAlertSettings() { const qc = useQueryClient(); const { addToast } = useToast(); return useMutation({ mutationFn: (payload: Parameters[0]) => adminApi.updateAlertSettings(payload), onSuccess: () => { qc.invalidateQueries({ queryKey: ['admin', 'alert-settings'] }); addToast('success', 'Alert settings updated'); }, onError: (error: Error) => { addToast('error', error.message || 'Failed to update alert settings'); }, }); } export function useTestAlert() { const { addToast } = useToast(); return useMutation({ mutationFn: () => adminApi.testAlertSettings(), onSuccess: (result) => { if (result.ok) addToast('success', 'Test alert sent — check Telegram.'); else addToast('error', result.error || 'Test alert failed'); }, onError: (error: Error) => { addToast('error', error.message || 'Test alert failed'); }, }); } 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: (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, }); } export function useToggleJob() { const qc = useQueryClient(); const { addToast } = useToast(); return useMutation({ mutationFn: ({ jobName, enabled }: { jobName: string; enabled: boolean }) => adminApi.toggleJob(jobName, enabled), onSuccess: () => { qc.invalidateQueries({ queryKey: ['admin', 'jobs'] }); }, onError: (error: Error) => { addToast('error', error.message || 'Failed to toggle job'); }, }); } export function useTriggerJob() { const qc = useQueryClient(); const { addToast } = useToast(); return useMutation({ mutationFn: (jobName: string) => adminApi.triggerJob(jobName), onSuccess: (result) => { qc.invalidateQueries({ queryKey: ['admin', 'jobs'] }); 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'); }, }); } // ── Data Cleanup ── export function useCleanupData() { const { addToast } = useToast(); return useMutation({ mutationFn: (olderThanDays: number) => adminApi.cleanupData(olderThanDays), onSuccess: (data) => { addToast('success', (data as { message: string }).message || 'Cleanup completed'); }, onError: (error: Error) => { addToast('error', error.message || 'Failed to cleanup data'); }, }); }