/** * Pending Task Recovery Component * Checks for pending tasks on page load and allows user to recover them */ "use client"; import { useState, useEffect, useCallback } from "react"; import { RefreshCw, Loader2, CheckCircle, XCircle, AlertCircle } from "lucide-react"; import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { api } from "@/lib/api"; import { getPendingTask, clearPendingTask, isPendingTaskValid, type PendingTask } from "@/lib/pending-task"; import { saveReport, checkDuplicateReport } from "@/lib/reports-db"; import { saveCloudReport, isCloudSyncEnabled } from "@/lib/user-api"; import { useAuth } from "@/contexts/auth-context"; export function PendingTaskRecovery() { const [pendingTask, setPendingTask] = useState(null); const [status, setStatus] = useState<'checking' | 'found' | 'recovering' | 'success' | 'failed' | 'not_found'>('checking'); const [message, setMessage] = useState(""); const { isAuthenticated } = useAuth(); useEffect(() => { // Check for pending tasks on mount const task = getPendingTask(); if (!task) { setStatus('not_found'); return; } if (!isPendingTaskValid(task)) { // Task is too old, clear it clearPendingTask(); setStatus('not_found'); return; } setPendingTask(task); setStatus('found'); }, []); const handleRecover = useCallback(async () => { if (!pendingTask) return; setStatus('recovering'); setMessage("正在檢查任務狀態..."); try { // Check task status const taskStatus = await api.getTaskStatus(pendingTask.taskId); if (taskStatus.status === 'completed' && taskStatus.result) { setMessage("分析已完成!正在儲存報告..."); // Check for duplicate const duplicate = await checkDuplicateReport( taskStatus.result.ticker, taskStatus.result.analysis_date ); if (duplicate) { setMessage("報告已存在於歷史記錄中"); clearPendingTask(); setStatus('success'); return; } // Save to local IndexedDB await saveReport( taskStatus.result.ticker, pendingTask.marketType, taskStatus.result.analysis_date, taskStatus.result, pendingTask.taskId ); // If authenticated, also save to cloud if (isAuthenticated && isCloudSyncEnabled()) { await saveCloudReport({ ticker: taskStatus.result.ticker, market_type: pendingTask.marketType, analysis_date: taskStatus.result.analysis_date, result: taskStatus.result, }); } clearPendingTask(); setMessage("報告已成功儲存到歷史記錄!"); setStatus('success'); } else if (taskStatus.status === 'failed') { clearPendingTask(); setMessage("分析任務失敗"); setStatus('failed'); } else if (taskStatus.status === 'running' || taskStatus.status === 'pending') { setMessage(`分析仍在進行中... (${taskStatus.progress || '處理中'})`); // Poll again after a delay setTimeout(() => { handleRecover(); }, 3000); } else { // Unknown status, clear it clearPendingTask(); setMessage("無法恢復任務"); setStatus('failed'); } } catch (error: any) { console.error("Recovery failed:", error); // If it's a 404, the task doesn't exist anymore if (error?.response?.status === 404) { clearPendingTask(); setMessage("任務已過期或不存在"); setStatus('failed'); } else { setMessage(`恢復失敗: ${error.message || '未知錯誤'}`); setStatus('failed'); } } }, [pendingTask, isAuthenticated]); const handleDismiss = () => { clearPendingTask(); setStatus('not_found'); setPendingTask(null); }; // Don't render if no pending task or already checked if (status === 'checking' || status === 'not_found') { return null; } const bgColor = status === 'success' ? 'bg-green-500/10 border-green-500' : status === 'failed' ? 'bg-red-500/10 border-red-500' : 'bg-yellow-500/10 border-yellow-500'; const Icon = status === 'success' ? CheckCircle : status === 'failed' ? XCircle : status === 'recovering' ? Loader2 : AlertCircle; const iconColor = status === 'success' ? 'text-green-500' : status === 'failed' ? 'text-red-500' : 'text-yellow-500'; return (

{status === 'found' && "發現未完成的分析任務"} {status === 'recovering' && "正在恢復分析結果..."} {status === 'success' && "報告恢復成功!"} {status === 'failed' && "恢復失敗"}

{status === 'found' && pendingTask && (

發現 {pendingTask.ticker} 的分析任務 (開始於 {new Date(pendingTask.startedAt).toLocaleString('zh-TW')})

)} {status === 'recovering' && (

{message}

)} {(status === 'success' || status === 'failed') && (

{message}

)}
); }