borsa / nextjs-app /src /components /PriceChart.tsx
veteroner's picture
fix: make sync endpoint market-aware — prevent US scan results from overwriting BIST file
ce7f322
'use client'
import { useEffect, useState } from 'react'
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer,
} from 'recharts'
import { formatCurrency } from '@/lib/utils'
interface PriceData {
date: string
close: number
high: number
low: number
volume: number
}
export function PriceChart({ symbol, market = 'bist' }: { symbol: string; market?: 'bist' | 'us' }) {
const isUS = market === 'us'
const cs = isUS ? '$' : '₺'
const [data, setData] = useState<PriceData[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [period, setPeriod] = useState(30)
useEffect(() => {
async function fetchPrices() {
setError(null)
setLoading(true)
try {
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), 15000)
const response = await fetch(`/api/stocks/${symbol}/prices?days=${period}`, {
signal: controller.signal,
})
clearTimeout(timeout)
if (!response.ok) {
throw new Error(isUS ? `Failed to fetch price data (HTTP ${response.status})` : `Fiyat verisi alınamadı (HTTP ${response.status})`)
}
const json = await response.json()
const prices = Array.isArray(json) ? json : (json?.data ?? json)
if (!Array.isArray(prices)) {
throw new Error(isUS ? 'Invalid data format' : 'Geçersiz veri formatı')
}
setData(prices)
} catch (err: unknown) {
const errObj = err instanceof Error ? err : null
if (errObj?.name === 'AbortError') {
setError(isUS ? 'Timeout - server not responding' : 'Zaman aşımı - sunucu yanıt vermedi')
} else {
setError(errObj?.message || (isUS ? 'Failed to load price data' : 'Fiyat verisi yüklenemedi'))
}
console.error('Failed to fetch prices:', err)
} finally {
setLoading(false)
}
}
fetchPrices()
}, [isUS, period, symbol])
if (loading) {
return (
<div className="bg-white rounded-lg shadow p-6">
<div className="h-96 bg-gray-100 rounded animate-pulse"></div>
</div>
)
}
return (
<div className="bg-white rounded-lg shadow p-6">
<div className="flex items-center justify-between mb-6">
<h2 className="text-xl font-bold text-gray-900">{isUS ? 'Price Chart' : 'Fiyat Grafiği'}</h2>
<div className="flex space-x-2">
{[7, 30, 90, 180].map((days) => (
<button
key={days}
onClick={() => setPeriod(days)}
className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${
period === days
? 'bg-primary-600 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
{days}G
</button>
))}
</div>
</div>
{data.length > 0 ? (
<ResponsiveContainer width="100%" height={400}>
<LineChart data={data}>
<CartesianGrid strokeDasharray="3 3" stroke="#e5e7eb" />
<XAxis
dataKey="date"
stroke="#6b7280"
tick={{ fontSize: 12 }}
/>
<YAxis
stroke="#6b7280"
tick={{ fontSize: 12 }}
tickFormatter={(value) => `${cs}${value}`}
/>
<Tooltip
contentStyle={{
backgroundColor: '#fff',
border: '1px solid #e5e7eb',
borderRadius: '8px',
}}
formatter={(value: number) => formatCurrency(value, market)}
/>
<Legend />
<Line
type="monotone"
dataKey="close"
stroke="#0ea5e9"
strokeWidth={2}
dot={false}
name={isUS ? 'Close' : 'Kapanış'}
/>
<Line
type="monotone"
dataKey="high"
stroke="#10b981"
strokeWidth={1}
dot={false}
strokeDasharray="5 5"
name={isUS ? 'High' : 'Yüksek'}
/>
<Line
type="monotone"
dataKey="low"
stroke="#ef4444"
strokeWidth={1}
dot={false}
strokeDasharray="5 5"
name={isUS ? 'Low' : 'Düşük'}
/>
</LineChart>
</ResponsiveContainer>
) : (
<div className="h-96 flex flex-col items-center justify-center text-gray-500 space-y-2">
{error ? (
<>
<svg className="w-12 h-12 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" />
</svg>
<p className="text-red-500 font-medium">{error}</p>
<button
onClick={() => setPeriod(p => p)}
className="mt-2 px-4 py-2 bg-blue-50 text-blue-600 rounded-md text-sm hover:bg-blue-100 transition-colors"
>
{isUS ? 'Retry' : 'Tekrar Dene'}
</button>
</>
) : (
<p>{isUS ? 'No price data found' : 'Fiyat verisi bulunamadı'}</p>
)}
</div>
)}
</div>
)
}