feat: İhaleist - Akıllı İhale Takip ve Analiz Platformu (Full PRD + 7 Enterprise Modül)

This commit is contained in:
2026-08-07 16:30:24 +03:00
commit ece74feefe
35 changed files with 7820 additions and 0 deletions
+288
View File
@@ -0,0 +1,288 @@
import React, { useState } from 'react';
import { useApp } from '../context/AppContext';
import { SECTORS_LIST, INSTITUTION_TYPES_LIST, CITIES_LIST } from '../data/mockTenders';
import {
Compass, Zap, Wrench, Calendar, Eye, EyeOff, FileText, CheckCircle2, XCircle, Archive,
Bot, PlusCircle, Trash2, ChevronDown, ChevronUp, Filter, MapPin, Building2, Tag
} from 'lucide-react';
export const LeftSidebar = () => {
const {
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: '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>
);
};