Files
ihaleist/src/components/LeftSidebar.jsx
T
hikmettan 3e129c2988 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ı
2026-08-28 08:49:10 +03:00

291 lines
12 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, { useState } from 'react';
import { useApp } from '../context/AppContext';
import { SECTORS_LIST, INSTITUTION_TYPES_LIST, CITIES_LIST } from '../data/constants';
import {
Compass, Zap, Wrench, Calendar, Eye, EyeOff, FileText, CheckCircle2, XCircle, Archive,
Bot, PlusCircle, Trash2, ChevronDown, ChevronUp, Filter, MapPin, Building2, Tag, Star
} from 'lucide-react';
export const LeftSidebar = () => {
const {
favoriteKurumlar,
quickFilter,
setQuickFilter,
setActiveTab,
savedFilters,
deleteSavedFilter,
selectedAlimTuru,
setSelectedAlimTuru,
selectedSektor,
setSelectedSektor,
selectedKurumTipi,
setSelectedKurumTipi,
selectedCity,
setSelectedCity,
clearAllFilters
} = useApp();
const [openSections, setOpenSections] = useState({
alimTuru: true,
sektorler: true,
kurumTipleri: false,
iller: false
});
const toggleSection = (key) => {
setOpenSections(prev => ({ ...prev, [key]: !prev[key] }));
};
const quickNavItems = [
{ id: 'all', label: 'Güncel Tüm İhaleler', icon: Compass },
{ id: 'favori_kurumlar', label: 'Favori Kurumlarım', icon: Star, badge: favoriteKurumlar?.length ? String(favoriteKurumlar.length) : null },
{ id: 'dogrudan_temin', label: 'Doğrudan Temin', icon: Zap, badge: 'Popüler' },
{ id: 'ihaleler', label: 'İhaleler (Açık/Pazarlık)', icon: Wrench },
{ id: 'bugun_eklenenler', label: 'Bugün Eklenenler', icon: Calendar },
{ id: 'incelenenler', label: 'İncelediğim İhaleler', icon: Eye },
{ id: 'incelenmedi', label: 'İncelemediklerim', icon: EyeOff },
{ id: 'sozlesmeler', label: 'Sözleşme İmzalananlar', icon: FileText },
{ id: 'sonuclananlar', label: 'Sonuçlananlar', icon: CheckCircle2 },
{ id: 'iptal', label: 'İptal Edilenler', icon: XCircle },
{ id: 'arsiv', label: 'İhale Arşivi', icon: Archive }
];
return (
<aside className="w-full lg:w-72 shrink-0 space-y-4">
{/* Hızlı Erişim Card */}
<div className="bg-white rounded-xl border border-slate-200 shadow-xs overflow-hidden">
<div className="px-4 py-3 border-b border-slate-100 bg-slate-50 flex items-center justify-between">
<span className="font-bold text-xs uppercase tracking-wider text-slate-700 flex items-center gap-1.5">
<Compass className="w-4 h-4 text-blue-600" /> Hızlı Erişim
</span>
{(quickFilter !== 'all' || selectedSektor || selectedCity || selectedAlimTuru || selectedKurumTipi) && (
<button
onClick={clearAllFilters}
className="text-[11px] font-medium text-blue-600 hover:underline"
>
Filtreleri Sıfırla
</button>
)}
</div>
<div className="p-1.5 divide-y divide-slate-100/50">
{quickNavItems.map(item => {
const Icon = item.icon;
const isSelected = quickFilter === item.id;
return (
<button
key={item.id}
onClick={() => {
setQuickFilter(item.id);
setActiveTab('list');
}}
className={`w-full flex items-center justify-between px-3 py-2 rounded-lg text-xs font-semibold transition-colors text-left ${
isSelected
? 'bg-blue-600 text-white shadow-xs'
: 'text-slate-700 hover:bg-slate-100'
}`}
>
<div className="flex items-center gap-2.5">
<Icon className={`w-4 h-4 ${isSelected ? 'text-white' : 'text-slate-400'}`} />
<span>{item.label}</span>
</div>
{item.badge && (
<span className={`text-[10px] px-1.5 py-0.5 rounded-full font-bold ${
isSelected ? 'bg-white/20 text-white' : 'bg-amber-100 text-amber-800'
}`}>
{item.badge}
</span>
)}
</button>
);
})}
</div>
</div>
{/* Kayıtlı Filtrelerim (Akıllı Filtre Botu) Card */}
<div className="bg-white rounded-xl border border-slate-200 shadow-xs p-4">
<div className="flex items-center justify-between mb-3">
<span className="font-bold text-xs uppercase tracking-wider text-slate-800 flex items-center gap-1.5">
<Bot className="w-4 h-4 text-blue-600" /> Kayıtlı Filtrelerim
</span>
<span className="text-[10px] bg-blue-100 text-blue-800 font-bold px-2 py-0.5 rounded-full">
{savedFilters.length} Bot
</span>
</div>
{savedFilters.length === 0 ? (
<div className="py-4 text-center">
<Bot className="w-8 h-8 text-slate-300 mx-auto mb-1.5" />
<p className="text-xs text-slate-400">Henüz aktif bir filtre yok.</p>
</div>
) : (
<div className="space-y-2 mb-3">
{savedFilters.map(filter => (
<div
key={filter.id}
className="bg-slate-50 hover:bg-slate-100 p-2.5 rounded-lg border border-slate-200 flex items-center justify-between text-xs transition-colors group"
>
<div className="flex-1 min-w-0 pr-2">
<p className="font-bold text-slate-800 truncate">{filter.name}</p>
<p className="text-[10px] text-slate-500 truncate mt-0.5">
👤 {filter.user} {filter.sehir && `• 📍 ${filter.sehir}`}
</p>
</div>
<button
onClick={() => deleteSavedFilter(filter.id)}
title="Filtreyi Sil"
className="text-slate-400 hover:text-red-600 p-1 opacity-60 group-hover:opacity-100 transition-opacity"
>
<Trash2 className="w-3.5 h-3.5" />
</button>
</div>
))}
</div>
)}
<button
onClick={() => setActiveTab('smart_filter_builder')}
className="w-full bg-blue-600 hover:bg-blue-500 text-white font-bold py-2 px-3 rounded-lg text-xs flex items-center justify-center gap-2 transition-colors shadow-xs"
>
<PlusCircle className="w-4 h-4" />
<span>Yeni Filtre Kur</span>
</button>
</div>
{/* Accordion Filters */}
<div className="bg-white rounded-xl border border-slate-200 shadow-xs overflow-hidden divide-y divide-slate-100">
{/* Alım Türü */}
<div>
<button
onClick={() => toggleSection('alimTuru')}
className="w-full px-4 py-3 flex items-center justify-between text-xs font-bold text-slate-800 bg-slate-50/50 hover:bg-slate-100 transition-colors"
>
<span className="flex items-center gap-2">
<Filter className="w-4 h-4 text-blue-600" /> Alım Türü
</span>
{openSections.alimTuru ? <ChevronUp className="w-4 h-4 text-slate-400" /> : <ChevronDown className="w-4 h-4 text-slate-400" />}
</button>
{openSections.alimTuru && (
<div className="p-3 space-y-1.5 text-xs">
{['Tümü', 'Doğrudan Temin', 'Açık İhale', 'Pazarlık Usulü'].map(turu => (
<label key={turu} className="flex items-center gap-2 cursor-pointer hover:bg-slate-50 p-1.5 rounded-md">
<input
type="radio"
name="alimTuru"
checked={(turu === 'Tümü' && !selectedAlimTuru) || selectedAlimTuru === turu}
onChange={() => {
setSelectedAlimTuru(turu === 'Tümü' ? '' : turu);
setActiveTab('list');
}}
className="accent-blue-600"
/>
<span className="text-slate-700 font-medium">{turu}</span>
</label>
))}
</div>
)}
</div>
{/* Sektörler */}
<div>
<button
onClick={() => toggleSection('sektorler')}
className="w-full px-4 py-3 flex items-center justify-between text-xs font-bold text-slate-800 bg-slate-50/50 hover:bg-slate-100 transition-colors"
>
<span className="flex items-center gap-2">
<Tag className="w-4 h-4 text-blue-600" /> Sektörler
</span>
{openSections.sektorler ? <ChevronUp className="w-4 h-4 text-slate-400" /> : <ChevronDown className="w-4 h-4 text-slate-400" />}
</button>
{openSections.sektorler && (
<div className="p-3 space-y-1 text-xs max-h-48 overflow-y-auto">
<button
onClick={() => { setSelectedSektor(''); setActiveTab('list'); }}
className={`w-full text-left px-2 py-1 rounded font-medium ${!selectedSektor ? 'bg-blue-50 text-blue-700 font-bold' : 'text-slate-600 hover:bg-slate-50'}`}
>
Tüm Sektörler
</button>
{SECTORS_LIST.map(sec => (
<button
key={sec}
onClick={() => { setSelectedSektor(sec); setActiveTab('list'); }}
className={`w-full text-left px-2 py-1 rounded truncate font-medium ${selectedSektor === sec ? 'bg-blue-50 text-blue-700 font-bold' : 'text-slate-600 hover:bg-slate-50'}`}
>
{sec}
</button>
))}
</div>
)}
</div>
{/* Kurum Tipleri */}
<div>
<button
onClick={() => toggleSection('kurumTipleri')}
className="w-full px-4 py-3 flex items-center justify-between text-xs font-bold text-slate-800 bg-slate-50/50 hover:bg-slate-100 transition-colors"
>
<span className="flex items-center gap-2">
<Building2 className="w-4 h-4 text-blue-600" /> Kurum Tipleri
</span>
{openSections.kurumTipleri ? <ChevronUp className="w-4 h-4 text-slate-400" /> : <ChevronDown className="w-4 h-4 text-slate-400" />}
</button>
{openSections.kurumTipleri && (
<div className="p-3 space-y-1 text-xs max-h-48 overflow-y-auto">
<button
onClick={() => { setSelectedKurumTipi(''); setActiveTab('list'); }}
className={`w-full text-left px-2 py-1 rounded font-medium ${!selectedKurumTipi ? 'bg-blue-50 text-blue-700 font-bold' : 'text-slate-600 hover:bg-slate-50'}`}
>
Tüm Kurumlar
</button>
{INSTITUTION_TYPES_LIST.map(k => (
<button
key={k}
onClick={() => { setSelectedKurumTipi(k); setActiveTab('list'); }}
className={`w-full text-left px-2 py-1 rounded truncate font-medium ${selectedKurumTipi === k ? 'bg-blue-50 text-blue-700 font-bold' : 'text-slate-600 hover:bg-slate-50'}`}
>
{k}
</button>
))}
</div>
)}
</div>
{/* İl Dağılımları */}
<div>
<button
onClick={() => toggleSection('iller')}
className="w-full px-4 py-3 flex items-center justify-between text-xs font-bold text-slate-800 bg-slate-50/50 hover:bg-slate-100 transition-colors"
>
<span className="flex items-center gap-2">
<MapPin className="w-4 h-4 text-blue-600" /> Şehir Seçimi
</span>
{openSections.iller ? <ChevronUp className="w-4 h-4 text-slate-400" /> : <ChevronDown className="w-4 h-4 text-slate-400" />}
</button>
{openSections.iller && (
<div className="p-3 space-y-1 text-xs max-h-48 overflow-y-auto">
<button
onClick={() => { setSelectedCity(''); setActiveTab('list'); }}
className={`w-full text-left px-2 py-1 rounded font-medium ${!selectedCity ? 'bg-blue-50 text-blue-700 font-bold' : 'text-slate-600 hover:bg-slate-50'}`}
>
Tüm Şehirler
</button>
{CITIES_LIST.map(city => (
<button
key={city}
onClick={() => { setSelectedCity(city); setActiveTab('list'); }}
className={`w-full text-left px-2 py-1 rounded font-medium ${selectedCity === city ? 'bg-blue-50 text-blue-700 font-bold' : 'text-slate-600 hover:bg-slate-50'}`}
>
{city}
</button>
))}
</div>
)}
</div>
</div>
</aside>
);
};