feat: tüm mock veriyi kaldır + Resend e-posta entegrasyonu

- mockTenders.js silindi; referans listeleri (sektör/il/kurum) constants.js'e taşındı
- AppContext: tenders/liveStats/competitorWatchlist/vaultDocuments/notifications/
  savedFilters/apiKeys/scrapedCapabilities → gerçek/boş; sahte Math.random online
  sayacı kaldırıldı; competitor & belge kasası artık localStorage
- LiveStatsBar: gerçek alanlar (canlı havuz/yüklü/bugün/kalem/takibimdeki)
- AnalyticsDashboard: KPI'lar (katılım/kazanma oranı/kazanılan bedel) + aylık hacim
  + sektörel dağılım gerçek companyStats/monthlyStats/sectorStats'tan; ciro hedefi
  kullanıcı tarafından belirlenir; veri yoksa dürüst boş durum
- ContractSearch & Rakip Radar: gerçek firmStats
- analytics.js: companyStats/monthlyStats/parseDateTR eklendi
- Resend: server/email.mjs + /api/notify/email/config + /api/notify/email/test
  (auth'lu, kullanıcının kendi adresine); PwaSettings gerçek e-posta test kartı
This commit is contained in:
2026-08-28 08:49:10 +03:00
parent 7521f798f6
commit 3e129c2988
14 changed files with 497 additions and 555 deletions
+28 -31
View File
@@ -1,42 +1,39 @@
import React from 'react';
import { useApp } from '../context/AppContext';
import { Database, PlusCircle, Eye } from 'lucide-react';
import { Database, PlusCircle, Eye, Layers } from 'lucide-react';
export const LiveStatsBar = () => {
const { liveStats } = useApp();
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);
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: liveStats.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: 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">
{/* Online Kullanıcı */}
<div className="flex items-center gap-1.5">
<span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse"></span>
<span className="font-bold text-white tracking-wide">{liveStats.onlineKullanici.toLocaleString('tr-TR')}</span>
<span className="text-slate-400">Online Kullanıcı</span>
</div>
{/* Toplam Kalem */}
<div className="flex items-center gap-1.5">
<Database className="w-3.5 h-3.5 text-blue-400" />
<span className="font-bold text-white tracking-wide">{liveStats.toplamKalem.toLocaleString('tr-TR')}</span>
<span className="text-slate-400">Toplam Kalem</span>
</div>
{/* Bugün Eklenen */}
<div className="flex items-center gap-1.5">
<PlusCircle className="w-3.5 h-3.5 text-emerald-400" />
<span className="font-bold text-emerald-400 tracking-wide">{liveStats.bugunEklenen.toLocaleString('tr-TR')}</span>
<span className="text-slate-400">Bugün Eklenen</span>
</div>
{/* Aktif Takip */}
<div className="flex items-center gap-1.5">
<Eye className="w-3.5 h-3.5 text-amber-400" />
<span className="font-bold text-white tracking-wide">{liveStats.aktifTakip.toLocaleString('tr-TR')}</span>
<span className="text-slate-400">Aktif Takip</span>
</div>
{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>
);
})}
</div>
</div>
);