'use client' import { useState, useEffect, useMemo, useCallback } from 'react' import { RefreshCw } from 'lucide-react' import { TIME_RANGE_CONFIGS, buildChartData } from './types' import type { TimeRange, ChartPoint, TimeRangeConfig } from './types' import { StockChartCore } from './stock-chart-core' import { cn } from '@/lib/utils' export interface StockChartProps { symbol?: string range?: TimeRange endDateTime?: string // ISO 8601 UTC if provided className?: string } function getNowUtcIso() { return new Date().toISOString().replace(/\.\d{3}Z$/, 'Z') } function resolveDurationForRequest(cfg: TimeRangeConfig) { if (cfg.label === '1D') return '4 D' if (cfg.label === '5D') return '8 D' if (cfg.label !== 'YTD') return cfg.duration const now = new Date() const jan1Utc = new Date(Date.UTC(now.getUTCFullYear(), 0, 1, 0, 0, 0)) const daysSince = Math.max( 1, Math.ceil((now.getTime() - jan1Utc.getTime()) / 86400000) ) return `${daysSince} D` } function buildHistoricalLineUrl( symbol: string, cfg: TimeRangeConfig, endDateTime?: string ) { const resolvedEndDateTime = endDateTime?.trim() || getNowUtcIso() const params = new URLSearchParams({ symbol: (symbol ?? '').trim().toUpperCase(), endDateTime: resolvedEndDateTime, duration: resolveDurationForRequest(cfg), barSize: cfg.barSize, whatToShow: 'Trades', useRth: 'true', }) return `https://api.dongfeng-systems.org/api/market/historical-line?${params.toString()}` } export function StockChart({ symbol, range = '1M', endDateTime, className, }: StockChartProps) { const [data, setData] = useState([]) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const safeSymbol = (symbol ?? '').trim().toUpperCase() const rangeConfig: TimeRangeConfig | undefined = useMemo( () => TIME_RANGE_CONFIGS.find((r) => r.label === range), [range] ) const validPrices = data .map((d) => d.price) .filter((price): price is number => price !== null) const minPrice = validPrices.length ? Math.min(...validPrices) : 0 const maxPrice = validPrices.length ? Math.max(...validPrices) : 0 const fetchData = useCallback(async () => { if (!safeSymbol) { setError('Missing symbol') setData([]) return } if (!rangeConfig) { setError(`Invalid range: ${range}`) setData([]) return } setLoading(true) setError(null) try { const url = buildHistoricalLineUrl(safeSymbol, rangeConfig, endDateTime) const res = await fetch(url, { method: 'GET', cache: 'no-store', }) if (!res.ok) { const text = await res.text() throw new Error(`HTTP ${res.status}: ${text}`) } const json = await res.json() if (!Array.isArray(json)) { throw new Error('Unexpected response format') } const chartData = buildChartData(json, range) setData(chartData) } catch (e) { setError(e instanceof Error ? e.message : 'Failed to load data') setData([]) } finally { setLoading(false) } }, [safeSymbol, rangeConfig, endDateTime, range]) useEffect(() => { fetchData() }, [fetchData]) return (
{loading && (

Loading {safeSymbol || 'chart'}...

)} {error && !loading && (

Failed to load data

{error}

)} {!loading && !error && data.length > 0 && rangeConfig && (
)} {!loading && !error && data.length === 0 && (

No data available for this range.

)}
{!loading && !error && data.length > 0 && rangeConfig && (
{data.length} bars ยท {rangeConfig.barSize} Range: ${minPrice.toFixed(2)} - ${maxPrice.toFixed(2)}
)}
) }