"use client"; import Link from "next/link"; import { useEffect, useMemo, useState } from "react"; import { MetricsPanel } from "../../components/MetricsPanel"; import { NewsList } from "../../components/NewsList"; import { PeriodTabs, periodSpec, type Period } from "../../components/PeriodTabs"; import { PredictionPanel } from "../../components/PredictionPanel"; import { PriceHero } from "../../components/PriceHero"; import { StockChart } from "../../components/StockChart"; import { SymbolSidebar } from "../../components/SymbolSidebar"; import { api, type ChartPayload, type LatestPredictionResponse } from "../../lib/api"; export default function CodePage({ params }: { params: { code: string } }) { const { code } = params; const [chart, setChart] = useState(null); const [prediction, setPrediction] = useState(null); const [err, setErr] = useState(null); const [period, setPeriod] = useState("3M"); const spec = periodSpec(period); const isIntraday = spec.interval === "10m"; useEffect(() => { let alive = true; setErr(null); setChart(null); const load = () => { api .getChart(code, spec.days, spec.interval) .then((c) => { if (alive) setChart(c); }) .catch((e) => { if (alive) setErr(e instanceof Error ? e.message : String(e)); }); }; load(); // 1일(10분봉) 모드만 60s 폴링 — 백엔드가 10분 내면 DB 만 읽음. if (isIntraday) { const h = window.setInterval(load, 60_000); return () => { alive = false; window.clearInterval(h); }; } return () => { alive = false; }; }, [code, spec.days, spec.interval, isIntraday]); useEffect(() => { let alive = true; api .latestPrediction(code) .then((r) => { if (alive && r.found) setPrediction(r); }) .catch(() => { // 예측 이력 없음 — 무시 }); return () => { alive = false; }; }, [code]); // 현재가/전일가 + 헤더 sparkline 시리즈 — ohlcv 끝부분에서 산출. const { current, prev, asOf, sparkSeries } = useMemo(() => { if (!chart || !chart.ohlcv.length) return { current: null, prev: null, asOf: null, sparkSeries: [] as number[] }; const valid = chart.ohlcv.filter((p) => p.close != null); if (!valid.length) return { current: null, prev: null, asOf: null, sparkSeries: [] as number[] }; const last = valid[valid.length - 1]; const prevPt = valid.length >= 2 ? valid[valid.length - 2] : null; // 최근 30개 종가 (없으면 가용 전체). const recent = valid.slice(-30).map((p) => p.close as number); return { current: last.close, prev: prevPt?.close ?? null, asOf: last.date, sparkSeries: recent, }; }, [chart]); return (
← 검색 setPeriod(id)} />
{err &&
차트 로딩 실패: {err}
} {chart && ( <>
{isIntraday && chart.intraday_status && (
실시간 10분봉 · 60초 갱신 · [{chart.intraday_status}]
)}
)}
); }