Files
ihaleist/src/components/tools/TurkeyHeatMap.jsx
T
hikmettan 0ab74143c0 feat: demo özelliklerini gerçekleştir — canlı veri + localStorage
- 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ı)
2026-08-28 01:58:46 +03:00

81 lines
3.8 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 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>
);
};