File size: 3,083 Bytes
3ed3f80
149698e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3ed3f80
 
 
 
 
149698e
 
 
 
 
3ed3f80
 
 
 
149698e
 
 
 
 
 
 
 
 
 
 
3ed3f80
 
 
 
 
 
 
149698e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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'),
  });
}