Files
ihaleist/src/components/Header.jsx
T
hikmettanandClaude Fable 5.1 08c0a0af7e Kullanıcı testi hataları: sabit tarihler, kişisel varsayılanlar, kılavuz düğmesi, mobil başlık, AI taslak çökmesi
- Günlük Bülten ve İhale Takvimi sabit "10.08.2026" yerine bugünden hesaplar;
  takvim 7 günü gerçek ihale tarihleriyle sayar
- Ekip/üye/firma varsayılanları oturumdaki kullanıcıdan; sahte "Ekip Notları"
  ve sabit "%94 Uyum" rozeti kaldırıldı (gerçek uyum skoru varsa gösterilir)
- Kılavuz düğmesi document.body'ye portal (ekran dışına düşüyordu)
- Detay modalı havuzdaki güncel kaydı okur: Takip Et / Görev aç etiketleri güncellenir
- Mobil: başlıkta ikincil düğmeler md altında gizli, arama formu küçülebilir
- PWA sayfası: sahte önbellek sayıları yerine Cache API ve bellek sayımı
- Sözleşme sorgula pazar payı bir ondalık (hep %0 görünüyordu)
- Rakip Radar "SMS" vaadi düzeltildi; sektör=CPV etiketi tekrarı gizlendi;
  Stripe kurulum uyarısı yalnız yöneticiye
- Açılışta 7 gereksiz PUT /api/me/data: ilk yükleme yazımı atlanır
- server: teklif taslağı çökmesi (kalemOku nesne döndürür), denetim yanıtından
  model adı kaldırıldı, gereklilikler satır bazlı; derin analiz 12k token +
  kesik JSON onarımı

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 09:43:41 +03:00

242 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 { Package, Bot, Search, SlidersHorizontal, Gift, Bell, User, ChevronDown, CheckCircle, LogOut, Globe, Sparkles, RefreshCw } from 'lucide-react';
export const Header = () => {
const {
searchQuery,
setSearchQuery,
setIsDetailedSearchOpen,
setIsUrunAramaOpen,
setIsCompanyScannerOpen,
setIsAiSpecAnalyzerOpen,
userProfile,
notifications,
setActiveTab,
clearAllFilters,
isRefreshingLive,
refreshLiveEKAPData,
authUser,
isAdmin,
plan,
handleLogout
} = useApp();
// Rozet SATIN ALINAN aboneliği gösterir. Yönetici hesapları her özelliğe
// erişir ama bu bir abonelik değildir; ikisini karıştırmamak için ayrı yazılır.
const planAdi = plan?.abonelikVar ? plan.abonelikAdi : (plan?.yonetici ? 'Yönetici' : 'Ücretsiz');
const [showNotifications, setShowNotifications] = useState(false);
const [showUserMenu, setShowUserMenu] = useState(false);
const handleSearchSubmit = (e) => {
e.preventDefault();
setActiveTab('list');
};
return (
<header className="bg-[#1f2937] text-white border-b border-slate-700 sticky top-0 z-40 shadow-sm">
<div className="max-w-[1600px] mx-auto px-4 h-16 flex items-center justify-between gap-4">
{/* Logo */}
<div
onClick={() => { setActiveTab('list'); clearAllFilters(); }}
className="flex items-center gap-2.5 cursor-pointer select-none group"
>
<div className="w-10 h-10 rounded-xl bg-blue-600 flex items-center justify-center shadow-md group-hover:bg-blue-500 transition-colors">
<Bot className="w-6 h-6 text-white" />
</div>
<div className="flex flex-col">
<span className="font-black text-2xl tracking-tight text-white flex items-center gap-1">
İhale<span className="text-blue-400 font-extrabold">ist</span>
</span>
</div>
</div>
{/* Search Bar */}
<form onSubmit={handleSearchSubmit} className="flex-1 min-w-0 max-w-2xl flex items-center gap-2">
<div className="relative flex-1">
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="İhale, kurum veya İKN no ile arayın..."
className="w-full bg-[#374151] text-white placeholder-slate-400 pl-4 pr-10 py-2 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 border border-slate-600 transition-all"
/>
{searchQuery && (
<button
type="button"
onClick={() => setSearchQuery('')}
className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-white text-xs"
>
✕
</button>
)}
</div>
<button
type="submit"
className="bg-slate-700 hover:bg-slate-600 text-white font-medium px-4 py-2 rounded-lg text-sm flex items-center gap-1.5 transition-colors border border-slate-600 shrink-0"
>
<Search className="w-4 h-4" />
<span className="font-semibold text-xs tracking-wider">ARA</span>
</button>
<button
type="button"
onClick={() => setIsUrunAramaOpen(true)}
title="Ürün Arama — ürün adınızı ihale dokümanlarının içinde arar"
className="bg-amber-400 hover:bg-amber-300 text-slate-950 font-black px-3 py-2 rounded-lg text-xs flex items-center gap-1.5 transition-colors shrink-0"
>
<Package className="w-4 h-4" />
<span className="hidden sm:inline tracking-wide">ÜRÜN ARA</span>
</button>
<button
type="button"
onClick={() => setIsDetailedSearchOpen(true)}
title="Detaylı İhale Arama"
className="bg-blue-600 hover:bg-blue-500 text-white p-2 rounded-lg text-sm flex items-center justify-center transition-colors shadow-sm shrink-0"
>
<SlidersHorizontal className="w-4 h-4" />
</button>
</form>
{/* Right Action Icons */}
<div className="flex items-center gap-2 sm:gap-3 shrink-0">
{/* Real Live EKAP Mode Badge */}
<button
onClick={refreshLiveEKAPData}
disabled={isRefreshingLive}
className="bg-emerald-600 hover:bg-emerald-500 text-white font-extrabold px-3 py-1.5 rounded-lg text-xs hidden md:hidden md:flex items-center gap-1.5 shadow-xs transition-colors shrink-0 disabled:opacity-50"
title="Canlı EKAP ve Doğrudan Temin Verilerini Anlık Güncelleyin"
>
<RefreshCw className={`w-3.5 h-3.5 text-white ${isRefreshingLive ? 'animate-spin' : ''}`} />
<span className="hidden lg:inline">{isRefreshingLive ? 'EKAP Çekiliyor...' : '🔴 CANLI EKAP VERİSİ'}</span>
</button>
{/* AI Şartname Analizi */}
<button
onClick={() => setIsAiSpecAnalyzerOpen(true)}
className="bg-purple-600 hover:bg-purple-500 text-white font-extrabold px-3 py-1.5 rounded-lg text-xs flex items-center gap-1.5 shadow-xs transition-colors shrink-0"
title="Teknik şartnameyi AI ile analiz edin"
>
<Sparkles className="w-4 h-4 text-amber-300" />
<span className="hidden md:inline">AI Şartname Analizi</span>
</button>
{/* Company Web Scanner Button */}
<button
onClick={() => setIsCompanyScannerOpen(true)}
className="bg-blue-600 hover:bg-blue-500 text-white font-bold px-3 py-1.5 rounded-lg text-xs hidden md:flex items-center gap-1.5 shadow-xs transition-colors shrink-0"
title="Web Sitenizi Tarayın & AI İhale Profiline İşleyin"
>
<Globe className="w-4 h-4 text-blue-200" />
<span className="hidden sm:inline">Web Siteni Tara (AI)</span>
</button>
{/* Trial Badge */}
<div className="bg-amber-400 text-slate-900 font-bold px-3 py-1.5 rounded-lg text-xs flex items-center gap-1.5 shadow-sm">
<Gift className="w-4 h-4 text-slate-900" />
<span>{userProfile.trialDaysLeft} gün</span>
</div>
{/* Notifications Dropdown */}
<div className="relative">
<button
onClick={() => setShowNotifications(!showNotifications)}
className="relative p-2 text-slate-300 hover:text-white hover:bg-slate-700 rounded-lg transition-colors"
>
<Bell className="w-5 h-5" />
{notifications.some(n => !n.read) && (
<span className="absolute top-1 right-1 w-2.5 h-2.5 bg-blue-500 rounded-full ring-2 ring-[#1f2937]" />
)}
</button>
{showNotifications && (
<div className="absolute right-0 mt-2 w-80 bg-white text-slate-800 rounded-xl shadow-xl border border-slate-200 py-2 z-50 animate-fade-in">
<div className="px-4 py-2 border-b border-slate-100 flex items-center justify-between">
<h4 className="font-bold text-sm">Bildirimler</h4>
<span className="text-xs text-blue-600 cursor-pointer hover:underline">Tümünü Okundu İşaretle</span>
</div>
<div className="max-h-64 overflow-y-auto divide-y divide-slate-100">
{notifications.map(n => (
<div key={n.id} className="p-3 hover:bg-slate-50 transition-colors cursor-pointer text-xs">
<div className="font-semibold text-slate-900 flex justify-between">
<span>{n.title}</span>
<span className="text-slate-400 font-normal text-[10px]">{n.time}</span>
</div>
<p className="text-slate-600 mt-1 leading-relaxed">{n.text}</p>
</div>
))}
</div>
</div>
)}
</div>
{/* User Profile */}
<div className="relative">
<button
onClick={() => setShowUserMenu(!showUserMenu)}
className="bg-slate-700 hover:bg-slate-600 text-white px-3 py-1.5 rounded-lg text-xs font-semibold flex items-center gap-2 transition-colors border border-slate-600"
>
{authUser?.picture ? (
<img src={authUser.picture} alt="" className="w-6 h-6 rounded-full object-cover" referrerPolicy="no-referrer" />
) : (
<div className="w-6 h-6 rounded-full bg-blue-600 flex items-center justify-center text-white font-bold text-xs">
{(authUser?.name || userProfile.username).charAt(0).toUpperCase()}
</div>
)}
<span>{(authUser?.name || userProfile.username).split(' ')[0]}</span>
<ChevronDown className="w-3.5 h-3.5 text-slate-400" />
</button>
{showUserMenu && (
<div className="absolute right-0 mt-2 w-60 bg-white text-slate-800 rounded-xl shadow-xl border border-slate-200 py-2 z-50 animate-fade-in">
<div className="px-4 py-2 border-b border-slate-100">
<p className="font-bold text-sm text-slate-900 flex items-center gap-1.5">
{authUser?.name || userProfile.name}
{isAdmin && <span className="text-[9px] font-black px-1.5 py-0.5 rounded bg-indigo-600 text-white">ADMIN</span>}
</p>
<p className="text-xs text-slate-500 truncate">{authUser?.email || userProfile.company}</p>
</div>
<div className="py-1 text-xs">
<button
onClick={() => { setShowUserMenu(false); setActiveTab('list'); }}
className="w-full text-left px-4 py-2 hover:bg-slate-50 flex items-center gap-2 text-slate-700"
>
<User className="w-4 h-4 text-slate-400" /> Firma Bilgileri & Üyeler
</button>
<button
onClick={() => { setShowUserMenu(false); setActiveTab('planlar'); }}
className="w-full text-left px-4 py-2 hover:bg-slate-50 flex items-center gap-2 text-slate-700"
>
<Gift className="w-4 h-4 text-amber-500" />
<span>Abonelik & Planlar</span>
<span className="ml-auto text-[10px] font-bold px-1.5 py-0.5 rounded-full bg-slate-100 text-slate-600 border border-slate-200">
{planAdi}
</span>
</button>
<button
onClick={() => { setShowUserMenu(false); setActiveTab('pwa_settings'); }}
className="w-full text-left px-4 py-2 hover:bg-slate-50 flex items-center gap-2 text-slate-700"
>
<CheckCircle className="w-4 h-4 text-slate-400" /> Bildirim Tercihleri
</button>
</div>
<div className="border-t border-slate-100 pt-1 text-xs">
<button
onClick={handleLogout}
className="w-full text-left px-4 py-2 text-red-600 hover:bg-red-50 flex items-center gap-2"
>
<LogOut className="w-4 h-4" /> Çıkış Yap
</button>
</div>
</div>
)}
</div>
</div>
</div>
</header>
);
};