116 lines
5.0 KiB
TypeScript
116 lines
5.0 KiB
TypeScript
import React from 'react'
|
|
import { Brain, RefreshCw } from 'lucide-react'
|
|
import { TradingChart } from './TradingChart'
|
|
|
|
const formatTime = (isoStr?: string) => {
|
|
if (!isoStr) return null
|
|
const d = new Date(isoStr)
|
|
return d.toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false, timeZone: 'Asia/Seoul' })
|
|
}
|
|
|
|
interface ChartPanelProps {
|
|
chartData: any[] | null | undefined
|
|
chartLoading: boolean
|
|
selectedStock: string
|
|
chartPeriod: 'D' | 'W' | 'M'
|
|
onPeriodChange: (period: 'D' | 'W' | 'M') => void
|
|
stockName: string
|
|
}
|
|
|
|
const ChartPanel = React.memo(({
|
|
chartData,
|
|
chartLoading,
|
|
selectedStock,
|
|
chartPeriod,
|
|
onPeriodChange,
|
|
stockName,
|
|
}: ChartPanelProps) => {
|
|
return (
|
|
<div className="bg-gray-800/50 backdrop-blur-sm border border-gray-700/50 rounded-xl p-1 shadow-lg h-[45%] flex flex-col min-h-0">
|
|
<div className="px-5 py-3 flex justify-between items-center border-b border-gray-700/30">
|
|
<div>
|
|
<h2 className="text-lg font-bold flex items-center gap-3 text-white">
|
|
{stockName || '종목을 선택해주세요'}
|
|
{selectedStock && (
|
|
<span className="text-xs font-normal text-gray-500 font-mono bg-gray-800 px-2 py-0.5 rounded">
|
|
{selectedStock}
|
|
</span>
|
|
)}
|
|
</h2>
|
|
</div>
|
|
<div className="flex bg-gray-900/50 rounded-lg p-1 border border-gray-700/50 scale-90">
|
|
{(['D', 'W', 'M'] as const).map((p) => (
|
|
<button
|
|
key={p}
|
|
onClick={() => onPeriodChange(p)}
|
|
className={`px-3 py-1 text-xs font-medium rounded-md transition-all ${chartPeriod === p
|
|
? 'bg-blue-600 text-white shadow-lg shadow-blue-900/20'
|
|
: 'text-gray-400 hover:text-white hover:bg-gray-800'
|
|
}`}
|
|
>
|
|
{p === 'D' ? '일봉' : p === 'W' ? '주봉' : '월봉'}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex-1 relative bg-[#0b0e14] rounded-b-lg overflow-hidden">
|
|
{chartLoading ? (
|
|
<div className="absolute inset-0 flex items-center justify-center text-gray-500 bg-[#0b0e14]">
|
|
<div className="flex flex-col items-center gap-2">
|
|
<RefreshCw className="animate-spin w-6 h-6 text-blue-500" />
|
|
<span className="text-sm">차트 로딩중...</span>
|
|
</div>
|
|
</div>
|
|
) : chartData && chartData.length > 0 ? (
|
|
<TradingChart data={chartData} />
|
|
) : (
|
|
<div className="absolute inset-0 flex items-center justify-center text-gray-600 text-sm">
|
|
데이터가 없습니다.
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
})
|
|
|
|
ChartPanel.displayName = 'ChartPanel'
|
|
|
|
// AI 분석 패널 — ChartPanel과 같은 파일에 배치 (center column 분리용)
|
|
interface AiAnalysisPanelProps {
|
|
aiAnalysisData: { analysis: string; data: any } | null | undefined
|
|
}
|
|
|
|
export const AiAnalysisPanel = React.memo(({ aiAnalysisData }: AiAnalysisPanelProps) => {
|
|
const aiAnalysisText = aiAnalysisData?.analysis || null
|
|
return (
|
|
<div className="bg-gray-800/50 backdrop-blur-sm border border-gray-700/50 rounded-xl p-4 shadow-lg flex flex-col">
|
|
<div className="flex justify-between items-center mb-2">
|
|
<h3 className="font-bold flex items-center gap-2 text-purple-400 text-sm">
|
|
<Brain size={16} />
|
|
AI 투자 분석
|
|
</h3>
|
|
{aiAnalysisData?.data?.analyzed_at && (
|
|
<span className="text-[10px] text-gray-500 font-mono">
|
|
{formatTime(aiAnalysisData.data.analyzed_at)}
|
|
</span>
|
|
)}
|
|
</div>
|
|
<div className="bg-gray-900/50 rounded-lg p-3 text-gray-300 text-sm leading-relaxed overflow-y-auto custom-scrollbar flex-1 border border-gray-700/30 whitespace-pre-wrap">
|
|
{aiAnalysisText ? (
|
|
typeof aiAnalysisText === 'string'
|
|
? aiAnalysisText
|
|
: JSON.stringify(aiAnalysisText, null, 2)
|
|
) : (
|
|
<div className="h-full flex items-center justify-center text-gray-600 text-center text-xs">
|
|
<p>종목을 선택하면<br />AI 투자 분석을 표시합니다.</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
})
|
|
|
|
AiAnalysisPanel.displayName = 'AiAnalysisPanel'
|
|
export default ChartPanel
|