76 lines
3.7 KiB
React
76 lines
3.7 KiB
React
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>
|
||
);
|
||
};
|