- analytics.js: firmStats/itemStats/provinceDensity (gerçek hesaplama) - Rakip Radar: canlı veriden kazanan firma analizi (mock 3 firma kaldırıldı) - Fiyat Analizi: gerçek kalem min/medyan/ort/maks + CSV export - TurkeyHeatMap: 81 il gerçek interaktif yoğunluk haritası (tıkla→filtrele) - Favori kurumlar: kart yıldızı + hızlı filtre (localStorage) - Ekip yönetimi: gerçek CRUD (RightSidebar) + Kanban ataması ekipten - Bildirimler: gerçek Notification API + izin + test + sessiz saatler - PDF: jsPDF ile gerçek birim fiyat teklif cetveli (kaşe/imza alanı)
81 lines
3.8 KiB
React
81 lines
3.8 KiB
React
import React, { useMemo } from 'react';
|
||
import { useApp } from '../../context/AppContext';
|
||
import { MapPin } from 'lucide-react';
|
||
import { provinceDensity, normProvince } from '../../services/analytics';
|
||
|
||
const ILLER = [
|
||
'Adana','Adıyaman','Afyonkarahisar','Ağrı','Amasya','Ankara','Antalya','Artvin','Aydın','Balıkesir',
|
||
'Bilecik','Bingöl','Bitlis','Bolu','Burdur','Bursa','Çanakkale','Çankırı','Çorum','Denizli',
|
||
'Diyarbakır','Edirne','Elazığ','Erzincan','Erzurum','Eskişehir','Gaziantep','Giresun','Gümüşhane','Hakkari',
|
||
'Hatay','Isparta','Mersin','İstanbul','İzmir','Kars','Kastamonu','Kayseri','Kırklareli','Kırşehir',
|
||
'Kocaeli','Konya','Kütahya','Malatya','Manisa','Kahramanmaraş','Mardin','Muğla','Muş','Nevşehir',
|
||
'Niğde','Ordu','Rize','Sakarya','Samsun','Siirt','Sinop','Sivas','Tekirdağ','Tokat',
|
||
'Trabzon','Tunceli','Şanlıurfa','Uşak','Van','Yozgat','Zonguldak','Aksaray','Bayburt','Karaman',
|
||
'Kırıkkale','Batman','Şırnak','Bartın','Ardahan','Iğdır','Yalova','Karabük','Kilis','Osmaniye','Düzce',
|
||
];
|
||
|
||
function heat(count, max) {
|
||
if (!count) return { bg: '#f1f5f9', fg: '#94a3b8' };
|
||
const r = count / (max || 1);
|
||
if (r > 0.66) return { bg: '#b91c1c', fg: '#fff' };
|
||
if (r > 0.4) return { bg: '#ef4444', fg: '#fff' };
|
||
if (r > 0.2) return { bg: '#f59e0b', fg: '#111' };
|
||
if (r > 0.08) return { bg: '#fcd34d', fg: '#111' };
|
||
return { bg: '#dbeafe', fg: '#1e40af' };
|
||
}
|
||
|
||
export const TurkeyHeatMap = () => {
|
||
const { tenders, setActiveTab, setSelectedCity } = useApp();
|
||
const density = useMemo(() => provinceDensity(tenders), [tenders]);
|
||
const max = Math.max(1, ...density.values());
|
||
const withData = ILLER.filter((il) => density.get(normProvince(il)));
|
||
const topPct = Math.round((withData.length / 81) * 100);
|
||
|
||
const openCity = (il) => {
|
||
setSelectedCity(il);
|
||
setActiveTab('list');
|
||
};
|
||
|
||
return (
|
||
<div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-xs space-y-4">
|
||
<div className="flex items-center justify-between border-b border-slate-100 pb-3 flex-wrap gap-2">
|
||
<div>
|
||
<h3 className="font-extrabold text-sm text-slate-900 flex items-center gap-2">
|
||
<MapPin className="w-4 h-4 text-red-600" /> İl Bazlı İhale Yoğunluk Haritası — Canlı
|
||
</h3>
|
||
<p className="text-xs text-slate-500">Yüklü ihalelerin illere dağılımı. Bir ile tıklayınca liste o ile filtrelenir. {withData.length}/81 ilde aktif ihale (%{topPct}).</p>
|
||
</div>
|
||
<div className="flex items-center gap-1.5 text-[10px] text-slate-500">
|
||
<span>Az</span>
|
||
<span className="w-4 h-3 rounded" style={{ background: '#dbeafe' }} />
|
||
<span className="w-4 h-3 rounded" style={{ background: '#fcd34d' }} />
|
||
<span className="w-4 h-3 rounded" style={{ background: '#f59e0b' }} />
|
||
<span className="w-4 h-3 rounded" style={{ background: '#ef4444' }} />
|
||
<span className="w-4 h-3 rounded" style={{ background: '#b91c1c' }} />
|
||
<span>Çok</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-3 sm:grid-cols-6 md:grid-cols-9 gap-1.5">
|
||
{ILLER.map((il) => {
|
||
const c = density.get(normProvince(il)) || 0;
|
||
const { bg, fg } = heat(c, max);
|
||
return (
|
||
<button
|
||
key={il}
|
||
onClick={() => c && openCity(il)}
|
||
disabled={!c}
|
||
title={`${il}: ${c} ihale`}
|
||
className={`rounded-lg p-2 text-center transition-transform ${c ? 'hover:scale-105 cursor-pointer' : 'cursor-default opacity-70'}`}
|
||
style={{ background: bg, color: fg }}
|
||
>
|
||
<div className="text-[10px] font-bold leading-tight truncate">{il}</div>
|
||
<div className="text-[13px] font-black leading-none mt-0.5">{c || '·'}</div>
|
||
</button>
|
||
);
|
||
})}
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|