| '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> |
| ) |
| } |
|
|