Spaces:
Running
Running
| import { useState, useCallback, useEffect } from 'react'; | |
| import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; | |
| import { api } from '../services/api'; | |
| import type { ScanPreset } from '@icc/shared'; | |
| interface ScanStartResponse { | |
| jobId: string; | |
| dateRange: any; | |
| status: string; | |
| } | |
| interface ScanStatusResponse { | |
| jobId: string; | |
| status: string; | |
| dateRange: any; | |
| progress: { | |
| emailsFound: number; | |
| emailsProcessed: number; | |
| emailsSkipped: number; | |
| emailsErrored: number; | |
| currentEmail?: string; | |
| }; | |
| startedAt: string; | |
| completedAt?: string; | |
| } | |
| export function useEmailScan() { | |
| const queryClient = useQueryClient(); | |
| const [activeJobId, setActiveJobId] = useState<string | null>(null); | |
| const startScanMutation = useMutation({ | |
| mutationFn: (params: { | |
| preset: ScanPreset; | |
| startDate?: string; | |
| endDate?: string; | |
| forceRescan?: boolean; | |
| }) => api.post<ScanStartResponse>('/scan/start', params), | |
| onSuccess: (data) => { | |
| setActiveJobId(data.jobId); | |
| }, | |
| }); | |
| const scanStatusQuery = useQuery<ScanStatusResponse>({ | |
| queryKey: ['scanStatus', activeJobId], | |
| queryFn: () => api.get<ScanStatusResponse>(`/scan/status/${activeJobId}`), | |
| enabled: !!activeJobId, | |
| retry: (failureCount, error: any) => { | |
| // Stop retrying on 404 (stale job ID after server restart) | |
| if (error?.status === 404 || error?.response?.status === 404) return false; | |
| return failureCount < 3; | |
| }, | |
| refetchInterval: (query) => { | |
| const data = query.state.data; | |
| if (data?.status === 'completed' || data?.status === 'failed') { | |
| return false; // Stop polling | |
| } | |
| // Stop polling on error (404, network error, etc.) | |
| if (query.state.status === 'error') { | |
| return false; | |
| } | |
| return 1000; // Poll every 1s while scanning | |
| }, | |
| }); | |
| const startScan = useCallback(( | |
| preset: ScanPreset, | |
| options?: { startDate?: string; endDate?: string; forceRescan?: boolean } | |
| ) => { | |
| startScanMutation.mutate({ preset, ...options }); | |
| }, [startScanMutation]); | |
| // Clear stale job ID on 404 (server restarted, job no longer exists) | |
| useEffect(() => { | |
| if (activeJobId && scanStatusQuery.isError) { | |
| setActiveJobId(null); | |
| } | |
| }, [activeJobId, scanStatusQuery.isError]); | |
| const isScanning = scanStatusQuery.data?.status === 'scanning' || | |
| scanStatusQuery.data?.status === 'parsing' || | |
| scanStatusQuery.data?.status === 'queued'; | |
| const clearJob = useCallback(() => { | |
| setActiveJobId(null); | |
| queryClient.invalidateQueries({ queryKey: ['transactions'] }); | |
| queryClient.invalidateQueries({ queryKey: ['transactionStats'] }); | |
| }, [queryClient]); | |
| return { | |
| startScan, | |
| isScanning, | |
| activeJobId, | |
| scanStatus: scanStatusQuery.data ?? null, | |
| scanError: startScanMutation.error, | |
| isStarting: startScanMutation.isPending, | |
| clearJob, | |
| }; | |
| } | |
| export function useScanHistory() { | |
| return useQuery({ | |
| queryKey: ['scanHistory'], | |
| queryFn: () => api.get<{ scans: any[] }>('/scan/history'), | |
| }); | |
| } | |