/** * History page - browse saved analysis reports */ "use client"; import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import { format } from "date-fns"; import { zhTW } from "date-fns/locale"; import { useAnalysisContext } from "@/context/AnalysisContext"; import { Button } from "@/components/ui/button"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Card, CardContent, CardHeader, CardTitle, CardDescription, CardFooter, } from "@/components/ui/card"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Trash2, Eye, RefreshCw, TrendingUp } from "lucide-react"; import { getReportsByMarketType, deleteReport, getReportCountByMarketType, type SavedReport, } from "@/lib/reports-db"; // Market type labels const MARKET_LABELS = { us: { label: "🇺🇸 美股", description: "美國股市分析報告" }, twse: { label: "🇹🇼 上市", description: "台灣證券交易所上市股票" }, tpex: { label: "🇹🇼 上櫃/興櫃", description: "台灣櫃買中心上櫃/興櫃股票" }, }; // Helper function to extract decision from trader report const extractDecisionFromReport = (report: SavedReport): { action: string; color: string } => { // First try the decision.action field if (report.result.decision?.action) { const action = report.result.decision.action; const actionLower = action.toLowerCase(); const color = actionLower.includes("buy") ? "text-green-600" : actionLower.includes("sell") ? "text-red-600" : "text-yellow-600"; return { action, color }; } // Fallback: try to extract from trader_investment_plan const traderReport = report.result.reports?.trader_investment_plan; if (traderReport) { const text = traderReport.toLowerCase(); // Look for Chinese decision keywords first if (text.includes("最終交易提案:買入") || text.includes("建議:買入")) { return { action: "買入", color: "text-green-600" }; } if (text.includes("最終交易提案:賣出") || text.includes("建議:賣出")) { return { action: "賣出", color: "text-red-600" }; } if (text.includes("最終交易提案:持有") || text.includes("建議:持有")) { return { action: "持有", color: "text-yellow-600" }; } // Look for English keywords if (text.includes("buy") && !text.includes("sell")) { return { action: "買入 (BUY)", color: "text-green-600" }; } if (text.includes("sell") && !text.includes("buy")) { return { action: "賣出 (SELL)", color: "text-red-600" }; } if (text.includes("hold")) { return { action: "持有 (HOLD)", color: "text-yellow-600" }; } } // Fallback: try final_trade_decision const finalDecision = report.result.reports?.final_trade_decision; if (finalDecision) { const text = finalDecision.toLowerCase(); if (text.includes("buy") || text.includes("買入")) { return { action: "買入", color: "text-green-600" }; } if (text.includes("sell") || text.includes("賣出")) { return { action: "賣出", color: "text-red-600" }; } if (text.includes("hold") || text.includes("持有")) { return { action: "持有", color: "text-yellow-600" }; } } return { action: "N/A", color: "text-gray-500" }; }; export default function HistoryPage() { const router = useRouter(); const { setAnalysisResult, setTaskId, setMarketType } = useAnalysisContext(); const [activeTab, setActiveTab] = useState<"us" | "twse" | "tpex">("us"); const [reports, setReports] = useState([]); const [loading, setLoading] = useState(true); const [counts, setCounts] = useState({ us: 0, twse: 0, tpex: 0 }); // Delete confirmation dialog const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [reportToDelete, setReportToDelete] = useState( null ); const [deleting, setDeleting] = useState(false); // Load reports when tab changes useEffect(() => { loadReports(); }, [activeTab]); // Load counts on mount useEffect(() => { loadCounts(); }, []); const loadReports = async () => { setLoading(true); try { const data = await getReportsByMarketType(activeTab); setReports(data); } catch (error) { console.error("Failed to load reports:", error); } finally { setLoading(false); } }; const loadCounts = async () => { try { const data = await getReportCountByMarketType(); setCounts(data); } catch (error) { console.error("Failed to load counts:", error); } }; const handleViewReport = (report: SavedReport) => { // Set the context with the saved report data setAnalysisResult(report.result); setTaskId(report.task_id || null); setMarketType(report.market_type); // Navigate to results page router.push("/analysis/results"); }; const handleDeleteClick = (report: SavedReport) => { setReportToDelete(report); setDeleteDialogOpen(true); }; const handleConfirmDelete = async () => { if (!reportToDelete?.id) return; setDeleting(true); try { await deleteReport(reportToDelete.id); // Refresh reports and counts await loadReports(); await loadCounts(); setDeleteDialogOpen(false); setReportToDelete(null); } catch (error) { console.error("Failed to delete report:", error); } finally { setDeleting(false); } }; return (
{/* Header */}

歷史報告

瀏覽已儲存的分析報告

{/* Market Type Tabs */} setActiveTab(v as typeof activeTab)} className="w-full animate-slide-up animate-delay-200" > {(Object.keys(MARKET_LABELS) as Array).map( (key) => ( {MARKET_LABELS[key].label} {counts[key]} ) )} {(Object.keys(MARKET_LABELS) as Array).map( (marketType) => (
{/* Refresh button */}
{/* Report List */} {loading ? (

載入中...

) : reports.length === 0 ? (

尚無{MARKET_LABELS[marketType].label}的分析報告

執行分析後,可在結果頁面儲存報告

) : (
{reports.map((report) => ( {report.ticker} {MARKET_LABELS[report.market_type].label} 分析日期:{report.analysis_date}

儲存時間: {format( new Date(report.saved_at), "yyyy/MM/dd HH:mm", { locale: zhTW } )}

{(() => { const decision = extractDecisionFromReport(report); return (

決策: {decision.action}

); })()}
))}
)}
) )}
{/* Delete Confirmation Dialog */} 確認刪除 確定要刪除 {reportToDelete?.ticker} 於{" "} {reportToDelete?.analysis_date} 的分析報告嗎?
此操作無法復原。
); }