borsa / nextjs-app /src /components /TopMovers.tsx
hfpush
fix: add ai/sentiment_model.py facade
c3c81a6
'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>
)
}