| 'use client' |
|
|
| import { useEffect, useState } from 'react' |
| import { TrendingUp, TrendingDown } from 'lucide-react' |
| import { formatPercent } from '@/lib/utils' |
|
|
| import { fetchJson } from '@/lib/http' |
|
|
| interface Mover { |
| symbol: string |
| name: string |
| change_pct: number |
| } |
|
|
| export function TopMovers() { |
| const [gainers, setGainers] = useState<Mover[]>([]) |
| const [losers, setLosers] = useState<Mover[]>([]) |
| const [loading, setLoading] = useState(true) |
|
|
| useEffect(() => { |
| async function fetchMovers() { |
| try { |
| const data = await fetchJson<Record<string, unknown>>( |
| `/api/movers`, |
| { method: 'GET' }, |
| { timeoutMs: 45000, retries: 2 }, |
| ) |
| setGainers(Array.isArray(data?.gainers) ? data.gainers : []) |
| setLosers(Array.isArray(data?.losers) ? data.losers : []) |
| } catch (error) { |
| console.error('Failed to fetch movers:', error) |
| setGainers([]) |
| setLosers([]) |
| } finally { |
| setLoading(false) |
| } |
| } |
|
|
| fetchMovers() |
| }, []) |
|
|
| if (loading) { |
| return ( |
| <div className="bg-white rounded-lg shadow p-6"> |
| <div className="h-6 bg-gray-200 rounded w-1/2 mb-4 animate-pulse"></div> |
| <div className="space-y-3"> |
| {[...Array(5)].map((_, i) => ( |
| <div key={i} className="h-12 bg-gray-100 rounded animate-pulse"></div> |
| ))} |
| </div> |
| </div> |
| ) |
| } |
|
|
| return ( |
| <div className="space-y-6"> |
| {/* Gainers */} |
| <div className="bg-white rounded-lg shadow p-6"> |
| <div className="flex items-center space-x-2 mb-4"> |
| <TrendingUp className="h-5 w-5 text-success" /> |
| <h3 className="text-lg font-semibold text-gray-900">En Çok Yükselenler</h3> |
| </div> |
| <div className="space-y-3"> |
| {gainers.slice(0, 5).map((stock) => ( |
| <div key={stock.symbol} className="flex items-center justify-between"> |
| <div> |
| <div className="font-semibold text-gray-900">{stock.symbol}</div> |
| <div className="text-xs text-gray-500 truncate max-w-[120px]"> |
| {stock.name} |
| </div> |
| </div> |
| <div className="text-success font-semibold"> |
| {formatPercent(stock.change_pct)} |
| </div> |
| </div> |
| ))} |
| </div> |
| {gainers.length === 0 && ( |
| <p className="text-sm text-gray-500 text-center py-4"> |
| Veri bulunamadı |
| </p> |
| )} |
| </div> |
| |
| {/* Losers */} |
| <div className="bg-white rounded-lg shadow p-6"> |
| <div className="flex items-center space-x-2 mb-4"> |
| <TrendingDown className="h-5 w-5 text-danger" /> |
| <h3 className="text-lg font-semibold text-gray-900">En Çok Düşenler</h3> |
| </div> |
| <div className="space-y-3"> |
| {losers.slice(0, 5).map((stock) => ( |
| <div key={stock.symbol} className="flex items-center justify-between"> |
| <div> |
| <div className="font-semibold text-gray-900">{stock.symbol}</div> |
| <div className="text-xs text-gray-500 truncate max-w-[120px]"> |
| {stock.name} |
| </div> |
| </div> |
| <div className="text-danger font-semibold"> |
| {formatPercent(stock.change_pct)} |
| </div> |
| </div> |
| ))} |
| </div> |
| {losers.length === 0 && ( |
| <p className="text-sm text-gray-500 text-center py-4"> |
| Veri bulunamadı |
| </p> |
| )} |
| </div> |
| </div> |
| ) |
| } |
|
|