Files
ihaleist/src/components/LiveStatsBar.jsx
T

76 lines
3.7 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useDeadlineClock } from '../hooks/useDeadlineClock';
import React, { useEffect, useState } from 'react';
import { useApp } from '../context/AppContext';
import { Database, PlusCircle, Eye, Layers, Trophy, TrendingDown } from 'lucide-react';
import { bugunEklendi, acikIhale } from '../services/ihaleTarih';
export const LiveStatsBar = () => {
const now = useDeadlineClock();
const { liveStats, tenders } = useApp();
const yukluIhale = tenders.length;
const takibimdeki = tenders.filter((t) => t.takipte).length;
const kalemSayisi = tenders.reduce((s, t) => s + (t.items?.length || 0), 0);
// Üst kartla (BUGÜN EKLENENLER) AYNI ölçüt: bugün yayımlanan ve süresi
// geçmemiş ilanlar (ilan.gov.tr + DT). İki bant farklı sayı göstermesin.
const bugunEklenen = tenders.length
? tenders.filter((t) => acikIhale(t, now) && bugunEklendi(t)).length
: liveStats.bugunEklenen;
// Havuz sağlığı: kaynak (ilan.gov.tr) sayısı 7 GÜNLÜK ORTALAMANIN çok altındaysa
// bunu açıkça söyleriz — kullanıcı "veri mi kayboldu?" diye düşünmesin.
// Ortalama, tek bir ölçüme kıyaslamaktan daha güvenilir: havuz yavaş yavaş
// erirse de yakalar, tek seferlik dalgalanmada boşuna uyarı vermez.
const [havuz, setHavuz] = useState(null);
useEffect(() => {
fetch('/api/health/havuz?limit=40')
.then((r) => r.json())
.then((d) => setHavuz(d.success ? d : null))
.catch(() => {});
}, []);
const dusuk = havuz?.yeterliVeri && havuz.ortalamayaOran != null && havuz.ortalamayaOran < 0.6;
const stats = [
{ deger: liveStats.toplamIhale, etiket: 'Canlı İhale Havuzu', renk: 'text-white', dot: true },
{ deger: yukluIhale, etiket: 'Yüklü İhale', renk: 'text-white', icon: Database, iconRenk: 'text-blue-400' },
{ deger: bugunEklenen, etiket: 'Bugün Eklenen', renk: 'text-emerald-400', icon: PlusCircle, iconRenk: 'text-emerald-400' },
{ deger: kalemSayisi, etiket: 'Kalem', renk: 'text-white', icon: Layers, iconRenk: 'text-indigo-400' },
{ deger: liveStats.arsiv, etiket: 'Sonuç Arşivi', renk: 'text-white', icon: Trophy, iconRenk: 'text-emerald-400' },
{ deger: takibimdeki, etiket: 'Takibimdeki', renk: 'text-white', icon: Eye, iconRenk: 'text-amber-400' },
];
return (
<div className="bg-[#111827] text-slate-300 py-1.5 px-4 text-xs font-medium border-b border-slate-800 shadow-inner">
<div className="max-w-[1600px] mx-auto flex items-center justify-center gap-6 sm:gap-10 flex-wrap">
{stats.map((s) => {
const Icon = s.icon;
return (
<div key={s.etiket} className="flex items-center gap-1.5">
{s.dot ? (
<span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse"></span>
) : (
<Icon className={`w-3.5 h-3.5 ${s.iconRenk}`} />
)}
<span className={`font-bold ${s.renk} tracking-wide`}>{(s.deger || 0).toLocaleString('tr-TR')}</span>
<span className="text-slate-400">{s.etiket}</span>
</div>
);
})}
{dusuk && (
<div
className="flex items-center gap-1.5 text-amber-300"
title={`ilan.gov.tr'de şu an ${havuz.son?.kaynak_toplam?.toLocaleString('tr-TR')} açık ihale var; son 7 günün ortalaması ${havuz.haftalikOrtalama?.toLocaleString('tr-TR')} (${havuz.olcumAdedi} ölçüm). Havuzun tamamı yüklendi — eksik veri yok.`}
>
<TrendingDown className="w-3.5 h-3.5" />
<span className="font-bold">
Kaynakta ihale sayısı düşük (7 gün ort. %{Math.round(havuz.ortalamayaOran * 100)})
</span>
</div>
)}
</div>
</div>
);
};