"use client"; import { useState, useEffect, useRef } from "react"; import { useQuery } from "@tanstack/react-query"; import { useApp } from "@/contexts/AppContext"; import { fetchProductHistory, HistoryPoint } from "@/lib/api/fundmarket"; import * as echarts from "echarts"; interface APYHistoryCardProps { productId: number; } export default function APYHistoryCard({ productId }: APYHistoryCardProps) { const { t } = useApp(); const [activeTab, setActiveTab] = useState<"apy" | "price">("apy"); const chartRef = useRef(null); const chartInstance = useRef(null); const { data: history = [] } = useQuery({ queryKey: ["product-history", productId], queryFn: () => fetchProductHistory(productId), staleTime: 60 * 60 * 1000, // 1h }); const colors = [ "#FBEADE", "#F5D4BE", "#EFBF9E", "#E9AA7E", "#E3955E", "#DD804E", "#D76B3E", "#D1562E", "#C65122", ]; const isEmpty = history.length < 2; const labels = isEmpty ? [] : history.map((p) => { const d = new Date(p.time); return `${d.getMonth() + 1}/${d.getDate()}`; }); const apyData = isEmpty ? [] : history.map((p) => p.apy); const priceData = isEmpty ? [] : history.map((p) => p.price); const activeData = activeTab === "apy" ? apyData : priceData; const highest = activeData.length > 0 ? Math.max(...activeData) : 0; const lowest = activeData.length > 0 ? Math.min(...activeData) : 0; const updateChart = () => { if (!chartInstance.current) return; // price tab 自适应 Y 轴,上下各留 20% padding;APY bar 图保持从 0 开始 let yAxisMin: number | undefined; let yAxisMax: number | undefined; if (activeTab === "price" && priceData.length > 0) { const yMin = Math.min(...priceData); const yMax = Math.max(...priceData); const range = yMax - yMin || yMax * 0.01 || 0.01; yAxisMin = yMin - range * 0.2; yAxisMax = yMax + range * 0.2; } const option: echarts.EChartsOption = { grid: { left: 2, right: 2, top: 10, bottom: 0 }, tooltip: { trigger: "axis", show: true, confine: true, backgroundColor: "rgba(17, 24, 39, 0.9)", borderColor: "#374151", textStyle: { color: "#f9fafb", fontSize: 12, fontWeight: 500 }, formatter: (params: any) => { const d = params[0]; const suffix = activeTab === "apy" ? "%" : " USDC"; return `
${labels[d.dataIndex]}
${Number(d.value).toFixed(activeTab === "apy" ? 2 : 4)}${suffix}
`; }, }, xAxis: { type: "category", data: labels, axisLine: { show: false }, axisTick: { show: false }, axisLabel: { show: labels.length > 0, color: "#9ca3af", fontSize: 10, fontWeight: 500, interval: Math.max(0, Math.floor(labels.length / 7) - 1), }, }, yAxis: { type: "value", min: yAxisMin, max: yAxisMax, axisLine: { show: false }, axisTick: { show: false }, axisLabel: { show: false }, splitLine: { show: false }, }, series: [ activeTab === "apy" ? { data: apyData.map((value, index) => ({ value, itemStyle: { color: colors[index % colors.length], borderRadius: [2, 2, 0, 0], }, })), type: "bar", barWidth: "60%", barMaxWidth: 24, barMinHeight: 2, } : { data: priceData, type: "line", smooth: true, symbol: "circle", symbolSize: 6, lineStyle: { color: "#10b981", width: 2 }, itemStyle: { color: "#10b981" }, areaStyle: { color: { type: "linear", x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: "rgba(16,185,129,0.3)" }, { offset: 1, color: "rgba(16,185,129,0)" }, ], }, }, }, ], }; chartInstance.current.setOption(option, true); }; useEffect(() => { // Use requestAnimationFrame to ensure the container has been laid out // before ECharts tries to measure its dimensions const frame = requestAnimationFrame(() => { if (!chartRef.current) return; if (!chartInstance.current) { chartInstance.current = echarts.init(chartRef.current); } updateChart(); chartInstance.current?.resize(); }); const handleResize = () => chartInstance.current?.resize(); window.addEventListener("resize", handleResize); return () => { cancelAnimationFrame(frame); window.removeEventListener("resize", handleResize); }; }, [activeTab, history]); useEffect(() => { return () => { chartInstance.current?.dispose(); }; }, []); return (
{/* Tabs */}
{/* Chart Area */}
{isEmpty ? t("apy.lastDays") : `${t("apy.lastDays")} (${history.length} snapshots)`}
{/* ECharts Chart */} {isEmpty ? (
{t("common.noData")}
) : (
)} {/* Stats */}
{t("apy.highest")} {isEmpty ? '--' : activeTab === "apy" ? `${highest.toFixed(2)}%` : `$${highest.toFixed(4)}`}
{t("apy.lowest")} {isEmpty ? '--' : activeTab === "apy" ? `${lowest.toFixed(2)}%` : `$${lowest.toFixed(4)}`}
); }