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
+180
View File
@@ -0,0 +1,180 @@
import React from 'react';
import { useApp } from '../../context/AppContext';
import { BarChart3, TrendingUp, Trophy, Target, MapPin, Building2, Award, ArrowUpRight, DollarSign } from 'lucide-react';
import { ResponsiveContainer, BarChart, Bar, XAxis, YAxis, Tooltip, CartesianGrid, PieChart, Pie, Cell } from 'recharts';
export const AnalyticsDashboard = () => {
const { tenders, userProfile } = useApp();
const monthlyData = [
{ month: 'Ocak', kazanim: 380000, katilim: 550000 },
{ month: 'Şubat', kazanim: 420000, katilim: 600000 },
{ month: 'Mart', kazanim: 510000, katilim: 720000 },
{ month: 'Nisan', kazanim: 490000, katilim: 680000 },
{ month: 'Mayıs', kazanim: 610000, katilim: 850000 },
{ month: 'Haziran', kazanim: 580000, katilim: 810000 },
{ month: 'Temmuz', kazanim: 410000, katilim: 590000 }
];
const pieData = [
{ name: 'Tıbbi Malzeme', value: 45, color: '#2563eb' },
{ name: 'Kırtasiye & Büro', value: 25, color: '#059669' },
{ name: 'Temizlik & Hijyen', value: 18, color: '#7c3aed' },
{ name: 'Bilişim & Donanım', value: 12, color: '#d97706' }
];
const cityHeatmap = [
{ city: 'Ankara', ihaleSayisi: 1420, ortalamaMaliyet: '₺240.000', yogunluk: 'Çok Yüksek (%88)', color: 'bg-red-500 text-white' },
{ city: 'Kayseri', ihaleSayisi: 850, ortalamaMaliyet: '₺180.000', yogunluk: 'Yüksek (%74)', color: 'bg-amber-500 text-white' },
{ city: 'İstanbul', ihaleSayisi: 2100, ortalamaMaliyet: '₺410.000', yogunluk: 'Çok Yüksek (%94)', color: 'bg-red-600 text-white' },
{ city: 'İzmir', ihaleSayisi: 980, ortalamaMaliyet: '₺210.000', yogunluk: 'Orta (%62)', color: 'bg-emerald-500 text-white' },
{ city: 'Antalya', ihaleSayisi: 640, ortalamaMaliyet: '₺165.000', yogunluk: 'Orta (%55)', color: 'bg-blue-500 text-white' }
];
return (
<div className="space-y-6 animate-fade-in">
{/* Header Banner */}
<div className="bg-linear-to-r from-slate-900 via-purple-950 to-indigo-900 text-white p-6 rounded-2xl shadow-lg border border-purple-800/40 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-2xl bg-purple-600 text-white flex items-center justify-center font-black shadow-md">
<BarChart3 className="w-6 h-6" />
</div>
<div>
<h2 className="font-extrabold text-xl">Analitik Dashboard & Fiyat Isı Haritası</h2>
<p className="text-xs text-purple-200 mt-0.5">{userProfile.company} firmasına özel kazanma oranı, ciro hedefleri ve il bazlı ihale yoğunluk analizi.</p>
</div>
</div>
</div>
{/* KPI Cards & Ciro Target */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-white p-5 rounded-2xl border border-slate-200 shadow-xs space-y-2">
<span className="text-xs font-bold text-slate-500 uppercase tracking-wider block">Katıldığımız İhaleler</span>
<div className="flex items-center justify-between">
<span className="text-2xl font-black text-slate-900">34 İhale</span>
<div className="w-8 h-8 rounded-lg bg-blue-50 text-blue-600 flex items-center justify-center">
<Trophy className="w-4 h-4" />
</div>
</div>
</div>
<div className="bg-white p-5 rounded-2xl border border-slate-200 shadow-xs space-y-2">
<span className="text-xs font-bold text-slate-500 uppercase tracking-wider block">İhale Kazanma Oranımız</span>
<div className="flex items-center justify-between">
<span className="text-2xl font-black text-emerald-600">%68</span>
<span className="bg-emerald-100 text-emerald-800 font-extrabold text-[10px] px-2 py-0.5 rounded-full flex items-center gap-0.5">
<ArrowUpRight className="w-3 h-3" /> +%12
</span>
</div>
</div>
<div className="bg-white p-5 rounded-2xl border border-slate-200 shadow-xs space-y-2">
<span className="text-xs font-bold text-slate-500 uppercase tracking-wider block">Toplam Kazanılan Bedel</span>
<div className="flex items-center justify-between">
<span className="text-2xl font-black text-indigo-900">₺3.400.000</span>
<div className="w-8 h-8 rounded-lg bg-indigo-50 text-indigo-600 flex items-center justify-center">
<Award className="w-4 h-4" />
</div>
</div>
</div>
{/* Target Progress Card */}
<div className="bg-linear-to-br from-blue-900 to-indigo-900 text-white p-5 rounded-2xl shadow-xs space-y-2">
<div className="flex justify-between items-center text-xs font-bold">
<span className="text-blue-200 uppercase tracking-wider flex items-center gap-1">
<Target className="w-3.5 h-3.5 text-amber-300" /> 2026 Ciro Hedefi
</span>
<span className="text-amber-300 font-black">%68</span>
</div>
<span className="text-xl font-black text-white block">₺3.4M / ₺5.0M</span>
<div className="w-full bg-blue-950 rounded-full h-2 overflow-hidden border border-blue-700">
<div className="bg-amber-400 h-2 rounded-full" style={{ width: '68%' }} />
</div>
</div>
</div>
{/* Recharts Row */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Monthly Revenue Chart */}
<div className="lg:col-span-2 bg-white p-5 rounded-2xl border border-slate-200 shadow-xs space-y-4">
<h3 className="font-extrabold text-sm text-slate-900 flex items-center gap-2">
<TrendingUp className="w-4 h-4 text-blue-600" /> Aylık İhale Kazanım ve Teklif Hacmi (TL)
</h3>
<div className="h-64 w-full">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={monthlyData}>
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#f1f5f9" />
<XAxis dataKey="month" tick={{ fontSize: 11 }} />
<YAxis tick={{ fontSize: 10 }} width={60} tickFormatter={(val) => `₺${val/1000}k`} />
<Tooltip formatter={(value) => [`₺${value.toLocaleString('tr-TR')}`, '']} />
<Bar dataKey="katilim" fill="#94a3b8" name="Toplam Verilen Teklif" radius={[4, 4, 0, 0]} />
<Bar dataKey="kazanim" fill="#2563eb" name="Kazanılan İhale Hacmi" radius={[4, 4, 0, 0]} />
</BarChart>
</ResponsiveContainer>
</div>
</div>
{/* Sector Distribution Pie */}
<div className="bg-white p-5 rounded-2xl border border-slate-200 shadow-xs space-y-4">
<h3 className="font-extrabold text-sm text-slate-900">Sektörel İhale Kazanım Dağılımı</h3>
<div className="h-48 w-full">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie data={pieData} dataKey="value" nameKey="name" cx="50%" cy="50%" innerRadius={40} outerRadius={70} paddingAngle={4}>
{pieData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
<Tooltip formatter={(value) => [`%${value}`, 'Oran']} />
</PieChart>
</ResponsiveContainer>
</div>
<div className="space-y-1.5 pt-2">
{pieData.map(item => (
<div key={item.name} className="flex justify-between items-center text-xs font-semibold">
<span className="flex items-center gap-2">
<span className="w-2.5 h-2.5 rounded-full" style={{ backgroundColor: item.color }}></span>
<span>{item.name}</span>
</span>
<span className="font-bold text-slate-900">%{item.value}</span>
</div>
))}
</div>
</div>
</div>
{/* City Price Heatmap Section */}
<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">
<div>
<h3 className="font-extrabold text-sm text-slate-900 flex items-center gap-2">
<MapPin className="w-4 h-4 text-red-600" /> Şehir & Kurum Bazlı İhale Yoğunluk Isı Haritası
</h3>
<p className="text-xs text-slate-500">Bölgelere göre ihale sayıları ve ortalama idare yaklaşık maliyet seviyeleri.</p>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-5 gap-3">
{cityHeatmap.map(item => (
<div key={item.city} className="bg-slate-50 p-4 rounded-xl border border-slate-200 space-y-2">
<div className="flex items-center justify-between">
<span className="font-extrabold text-sm text-slate-900">{item.city}</span>
<span className={`text-[9px] font-black px-2 py-0.5 rounded-full ${item.color}`}>
{item.yogunluk}
</span>
</div>
<p className="text-xs text-slate-600 font-medium">İhale Sayısı: <strong className="text-slate-900">{item.ihaleSayisi}</strong></p>
<p className="text-xs text-slate-600 font-medium">Ort. Bedel: <strong className="text-indigo-900">{item.ortalamaMaliyet}</strong></p>
</div>
))}
</div>
</div>
</div>
);
};
+174
View File
@@ -0,0 +1,174 @@
import React, { useState } from 'react';
import { useApp } from '../../context/AppContext';
import { Code, Key, Webhook, Send, CheckCircle2, Copy, Plus, Lock, Globe, MessageSquare, Terminal, RefreshCw } from 'lucide-react';
import confetti from 'canvas-confetti';
export const ApiWebhooks = () => {
const { apiKeys, setApiKeys, webhooksConfig, setWebhooksConfig } = useApp();
const [copiedKey, setCopiedKey] = useState(false);
const [testSuccess, setTestSuccess] = useState(false);
const handleGenerateApiKey = () => {
const newKey = {
id: 'key-' + Date.now(),
name: 'Yeni Uygulama Anahtarı ' + (apiKeys.length + 1),
key: 'ih_live_' + Math.random().toString(36).substring(2, 18),
createdAt: new Date().toISOString().split('T')[0],
lastUsed: 'Henüz Kullanılmadı'
};
setApiKeys(prev => [newKey, ...prev]);
confetti({ particleCount: 50 });
};
const handleSendTestWebhook = () => {
setTestSuccess(true);
confetti({ particleCount: 60 });
setTimeout(() => setTestSuccess(false), 4000);
};
return (
<div className="space-y-6 animate-fade-in">
{/* Header */}
<div className="bg-linear-to-r from-slate-900 via-emerald-950 to-teal-900 text-white p-6 rounded-2xl shadow-lg border border-emerald-800/40 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-2xl bg-emerald-600 text-white flex items-center justify-center font-black shadow-md">
<Webhook className="w-6 h-6" />
</div>
<div>
<h2 className="font-extrabold text-xl">API & Webhook Entegrasyon Yönetimi</h2>
<p className="text-xs text-emerald-200 mt-0.5">ERP, SAP, Logo, Mikro muhasebe yazılımlarınız ve Slack/Teams kanallarınız ile çift yönlü canlı veri aktarımı.</p>
</div>
</div>
</div>
{/* REST API Keys Section */}
<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">
<div>
<h3 className="font-extrabold text-sm text-slate-900 flex items-center gap-2">
<Key className="w-4 h-4 text-emerald-600" /> REST API Canlı Erişim Anahtarları
</h3>
<p className="text-xs text-slate-500">Geliştiricileriniz için RESTful JSON API anahtarları.</p>
</div>
<button
onClick={handleGenerateApiKey}
className="bg-emerald-600 hover:bg-emerald-500 text-white font-extrabold text-xs px-4 py-2 rounded-xl flex items-center gap-1.5 transition-colors shadow-xs"
>
<Plus className="w-4 h-4" />
<span>Yeni API Anahtarı Üret</span>
</button>
</div>
<div className="space-y-3">
{apiKeys.map(k => (
<div key={k.id} className="bg-slate-50 p-4 rounded-xl border border-slate-200 flex flex-col md:flex-row md:items-center justify-between gap-3">
<div className="space-y-1">
<span className="font-bold text-xs text-slate-900">{k.name}</span>
<div className="flex items-center gap-2">
<code className="bg-slate-900 text-amber-400 font-mono text-xs px-3 py-1 rounded-lg">
{k.key}
</code>
<button
onClick={() => {
navigator.clipboard.writeText(k.key);
setCopiedKey(true);
setTimeout(() => setCopiedKey(false), 2000);
}}
className="p-1.5 text-slate-500 hover:text-slate-900 hover:bg-slate-200 rounded-lg transition-colors text-xs font-bold flex items-center gap-1"
>
<Copy className="w-3.5 h-3.5" />
<span>{copiedKey ? 'Kopyalandı!' : 'Kopyala'}</span>
</button>
</div>
</div>
<div className="text-[11px] text-slate-500 font-medium space-y-0.5">
<p>Oluşturulma: {k.createdAt}</p>
<p>Son Kullanım: <strong className="text-emerald-700">{k.lastUsed}</strong></p>
</div>
</div>
))}
</div>
</div>
{/* Webhook Configuration & Slack / Teams Section */}
<div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-xs space-y-6">
<div className="border-b border-slate-100 pb-3">
<h3 className="font-extrabold text-sm text-slate-900 flex items-center gap-2">
<MessageSquare className="w-4 h-4 text-blue-600" /> Slack & Microsoft Teams Webhook Ayarları
</h3>
<p className="text-xs text-slate-500">İhale etkinlikleri gerçekleştiğinde anında bildirim alacağınız HTTP Webhook URL adresleri.</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<label className="block text-xs font-bold text-slate-800 uppercase tracking-wider">Slack Webhook URL</label>
<input
type="url"
value={webhooksConfig.slackWebhookUrl}
onChange={(e) => setWebhooksConfig({ ...webhooksConfig, slackWebhookUrl: e.target.value })}
className="w-full bg-slate-50 border border-slate-300 rounded-xl px-4 py-2.5 text-xs font-mono text-slate-800"
/>
</div>
<div className="space-y-2">
<label className="block text-xs font-bold text-slate-800 uppercase tracking-wider">Microsoft Teams Webhook URL</label>
<input
type="url"
value={webhooksConfig.teamsWebhookUrl}
onChange={(e) => setWebhooksConfig({ ...webhooksConfig, teamsWebhookUrl: e.target.value })}
className="w-full bg-slate-50 border border-slate-300 rounded-xl px-4 py-2.5 text-xs font-mono text-slate-800"
/>
</div>
</div>
{/* Event Trigger Checkboxes */}
<div className="space-y-3 pt-2">
<label className="block text-xs font-bold text-slate-800 uppercase tracking-wider">Tetiklenecek Webhook Etkinlikleri</label>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{[
{ key: 'newTenderMatch', title: '🟢 Yeni Uyumlu İhale Yayınlandığında' },
{ key: 'tenderWon', title: '🏆 İhale Kazanıldığında ERP/Muhasebeye Gönder' },
{ key: 'bidDeadlineAlert', title: '⚠️ İhale Son Başvurusuna 3 Saat Kala Uyar' },
{ key: 'competitorActivity', title: '🛡️ İzlenen Rakip Firma Teklif Verince Bildir' }
].map(item => (
<label key={item.key} className="flex items-center gap-3 p-3 bg-slate-50 hover:bg-slate-100 rounded-xl border border-slate-200 cursor-pointer font-semibold text-xs text-slate-800">
<input
type="checkbox"
checked={webhooksConfig.events[item.key]}
onChange={(e) => setWebhooksConfig({
...webhooksConfig,
events: { ...webhooksConfig.events, [item.key]: e.target.checked }
})}
className="w-4 h-4 accent-emerald-600 rounded"
/>
<span>{item.title}</span>
</label>
))}
</div>
</div>
{/* Test Webhook Button */}
<div className="pt-2 flex items-center gap-3">
<button
onClick={handleSendTestWebhook}
className="bg-slate-900 hover:bg-slate-800 text-white font-extrabold text-xs px-6 py-3 rounded-xl flex items-center gap-2 shadow-md transition-colors"
>
<Send className="w-4 h-4 text-emerald-400" />
<span>Test Webhook Payload Gönder (HTTP 200 OK)</span>
</button>
{testSuccess && (
<span className="text-xs font-extrabold text-emerald-700 bg-emerald-50 border border-emerald-200 px-3 py-1.5 rounded-xl flex items-center gap-1.5 animate-fade-in">
<CheckCircle2 className="w-4 h-4 text-emerald-600" />
<span>Test Webhook Başarıyla Slack & Teams Kanallarına İletildi! (HTTP 200 OK)</span>
</span>
)}
</div>
</div>
</div>
);
};
+174
View File
@@ -0,0 +1,174 @@
import React, { useState } from 'react';
import { useApp } from '../../context/AppContext';
import { Radar, Bell, Plus, ShieldAlert, Award, FileText, CheckCircle2, Trash2, ArrowRight } from 'lucide-react';
import confetti from 'canvas-confetti';
export const CompetitorRadar = () => {
const { competitorWatchlist, setCompetitorWatchlist, setActiveTab } = useApp();
const [newCompetitorName, setNewCompetitorName] = useState('');
const [newSector, setNewSector] = useState('Medikal & Tıbbi Malzeme');
const handleAddCompetitor = (e) => {
e.preventDefault();
if (!newCompetitorName.trim()) return;
const newComp = {
id: 'cw-' + Date.now(),
name: newCompetitorName,
sector: newSector,
winCount: 0,
totalRevenue: 0,
marketShare: 5,
status: 'Aktif İzleniyor',
alertOnBid: true,
alertOnWin: true
};
setCompetitorWatchlist(prev => [newComp, ...prev]);
setNewCompetitorName('');
confetti({ particleCount: 50 });
};
const toggleAlert = (id, field) => {
setCompetitorWatchlist(prev => prev.map(c => c.id === id ? { ...c, [field]: !c[field] } : c));
};
const removeCompetitor = (id) => {
setCompetitorWatchlist(prev => prev.filter(c => c.id !== id));
};
return (
<div className="space-y-6 animate-fade-in">
{/* Header Banner */}
<div className="bg-linear-to-r from-slate-900 via-indigo-950 to-blue-900 text-white p-6 rounded-2xl shadow-lg border border-indigo-800/40 flex flex-col md:flex-row items-start md:items-center justify-between gap-4">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-2xl bg-indigo-600 text-white flex items-center justify-center font-black shadow-md">
<Radar className="w-6 h-6 animate-pulse" />
</div>
<div>
<div className="flex items-center gap-2">
<h2 className="font-extrabold text-xl">Rakip Radar & Proaktif İhale Uyarısı</h2>
<span className="bg-indigo-500 text-white text-[10px] font-extrabold px-2.5 py-0.5 rounded-full uppercase tracking-wider">
Proaktif Takip
</span>
</div>
<p className="text-xs text-indigo-200 mt-0.5">İzlemeye aldığınız rakip firmalar ihaleye teklif verdiğinde veya kazandığında anında SMS ve Push bildirimi alın.</p>
</div>
</div>
<button
onClick={() => setActiveTab('sozlesme')}
className="bg-white/10 hover:bg-white/20 text-white font-bold text-xs px-4 py-2.5 rounded-xl border border-white/20 transition-colors flex items-center gap-2 shrink-0"
>
<FileText className="w-4 h-4 text-amber-400" />
<span>Sözleşme Sorgulama İncele</span>
<ArrowRight className="w-3.5 h-3.5" />
</button>
</div>
{/* Add New Competitor Form */}
<form onSubmit={handleAddCompetitor} className="bg-white p-5 rounded-2xl border border-slate-200 shadow-xs flex flex-col md:flex-row gap-3 items-end">
<div className="flex-1 space-y-1.5 w-full">
<label className="block text-xs font-bold text-slate-800 uppercase tracking-wider">İzlenecek Rakip Firma Adı</label>
<input
type="text"
required
value={newCompetitorName}
onChange={(e) => setNewCompetitorName(e.target.value)}
placeholder="Örn: ABC Medikal Sanayi ve Ticaret A.Ş."
className="w-full bg-slate-50 border border-slate-300 rounded-xl px-4 py-2.5 text-xs font-semibold text-slate-900 focus:ring-2 focus:ring-indigo-500"
/>
</div>
<div className="w-full md:w-64 space-y-1.5">
<label className="block text-xs font-bold text-slate-800 uppercase tracking-wider">Sektör</label>
<select
value={newSector}
onChange={(e) => setNewSector(e.target.value)}
className="w-full bg-slate-50 border border-slate-300 rounded-xl px-3 py-2.5 text-xs font-semibold text-slate-900"
>
<option>Medikal & Tıbbi Malzeme</option>
<option>Sağlık & Büro Malzemeleri</option>
<option>Laboratuvar & Bilişim</option>
<option>Kırtasiye & Sanatsal</option>
<option>Temizlik & Hijyen</option>
</select>
</div>
<button
type="submit"
className="w-full md:w-auto bg-indigo-600 hover:bg-indigo-500 text-white font-extrabold text-xs px-6 py-3 rounded-xl flex items-center justify-center gap-2 shadow-md transition-colors shrink-0"
>
<Plus className="w-4 h-4" />
<span>Radara Ekle</span>
</button>
</form>
{/* Tracked Competitors Table */}
<div className="bg-white rounded-2xl border border-slate-200 shadow-xs overflow-hidden">
<div className="px-6 py-4 border-b border-slate-100 bg-slate-50/50 flex items-center justify-between">
<h3 className="font-extrabold text-sm text-slate-900 flex items-center gap-2">
<ShieldAlert className="w-4 h-4 text-indigo-600" /> Takipteki Rakip Firmalar ({competitorWatchlist.length})
</h3>
<span className="text-xs text-slate-500">Otomatik bildirimler aktif</span>
</div>
<div className="divide-y divide-slate-100">
{competitorWatchlist.map((comp) => (
<div key={comp.id} className="p-5 hover:bg-slate-50/80 transition-colors flex flex-col md:flex-row md:items-center justify-between gap-4">
{/* Info */}
<div className="space-y-1">
<div className="flex items-center gap-2">
<h4 className="font-bold text-sm text-slate-900">{comp.name}</h4>
<span className="bg-indigo-100 text-indigo-800 font-bold text-[10px] px-2 py-0.5 rounded-full">
{comp.sector}
</span>
</div>
<div className="flex items-center gap-4 text-xs text-slate-500 font-medium">
<span>🏆 Kazandığı İhale: <strong className="text-slate-800">{comp.winCount}</strong></span>
<span>💰 Toplam Hacim: <strong className="text-emerald-700">₺{(comp.totalRevenue / 1000000).toFixed(1)}M</strong></span>
<span>📊 Tahmini Pazar Payı: <strong className="text-indigo-900">%{comp.marketShare}</strong></span>
</div>
</div>
{/* Notification Toggles & Actions */}
<div className="flex items-center gap-3 shrink-0">
<button
onClick={() => toggleAlert(comp.id, 'alertOnBid')}
className={`text-xs font-extrabold px-3 py-1.5 rounded-xl border flex items-center gap-1.5 transition-colors ${
comp.alertOnBid ? 'bg-amber-50 text-amber-800 border-amber-300' : 'bg-slate-100 text-slate-400 border-slate-200'
}`}
>
<Bell className="w-3.5 h-3.5" />
<span>Teklif Verince Uyar</span>
</button>
<button
onClick={() => toggleAlert(comp.id, 'alertOnWin')}
className={`text-xs font-extrabold px-3 py-1.5 rounded-xl border flex items-center gap-1.5 transition-colors ${
comp.alertOnWin ? 'bg-emerald-50 text-emerald-800 border-emerald-300' : 'bg-slate-100 text-slate-400 border-slate-200'
}`}
>
<Award className="w-3.5 h-3.5" />
<span>Kazanırsa Uyar</span>
</button>
<button
onClick={() => removeCompetitor(comp.id)}
className="p-2 text-slate-400 hover:text-red-600 rounded-lg transition-colors"
title="Radardan Çıkar"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
</div>
))}
</div>
</div>
</div>
);
};
+78
View File
@@ -0,0 +1,78 @@
import React, { useState } from 'react';
import { COMPETITOR_DATA } from '../../data/mockTenders';
import { FileCheck2, Search, Building2, TrendingUp, Award, Shield } from 'lucide-react';
export const ContractSearch = () => {
const [searchTerm, setSearchTerm] = useState('');
const filteredCompetitors = COMPETITOR_DATA.filter(c =>
c.name.toLowerCase().includes(searchTerm.toLowerCase())
);
return (
<div className="space-y-4 my-4 animate-fade-in">
<div className="bg-white rounded-xl border border-slate-200 p-5 shadow-xs flex flex-col md:flex-row items-center justify-between gap-4">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-indigo-600 text-white flex items-center justify-center shadow-md">
<FileCheck2 className="w-6 h-6" />
</div>
<div>
<h1 className="text-xl font-black text-slate-900">Sözleşme Bedeli & Rakip Analizi</h1>
<p className="text-xs text-slate-500">Rakip firmaların kamu ihalelerinden aldığı toplam iş hacmini ve sözleşme bedellerini sorgulayın.</p>
</div>
</div>
<div className="relative w-full md:w-80">
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Firma adı ile arayın..."
className="w-full bg-slate-50 border border-slate-300 rounded-lg pl-9 pr-4 py-2 text-xs font-semibold"
/>
<Search className="w-4 h-4 text-slate-400 absolute left-3 top-1/2 -translate-y-1/2" />
</div>
</div>
{/* Competitor Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{filteredCompetitors.map((comp, idx) => (
<div key={idx} className="bg-white rounded-xl border border-slate-200 p-5 shadow-xs hover:shadow-md transition-all space-y-4">
<div className="flex items-start justify-between border-b border-slate-100 pb-3">
<div>
<span className="text-[10px] font-extrabold bg-indigo-100 text-indigo-800 px-2 py-0.5 rounded">
Sektör Sırası #{idx + 1}
</span>
<h3 className="font-extrabold text-base text-slate-900 mt-1">{comp.name}</h3>
</div>
<Award className="w-6 h-6 text-indigo-600 shrink-0" />
</div>
<div className="grid grid-cols-2 gap-3 text-xs">
<div className="bg-slate-50 p-3 rounded-lg border border-slate-200">
<span className="text-slate-400 block font-medium">Toplam İş Hacmi</span>
<span className="text-sm font-black text-indigo-900 mt-0.5 block">{comp.totalVolume}</span>
</div>
<div className="bg-slate-50 p-3 rounded-lg border border-slate-200">
<span className="text-slate-400 block font-medium">Kazandığı İhale</span>
<span className="text-sm font-black text-emerald-700 mt-0.5 block">{comp.winCount} Adet İhale</span>
</div>
</div>
<div>
<div className="flex justify-between text-xs font-semibold text-slate-700 mb-1">
<span>Tahmini Pazar Payı</span>
<span className="font-bold text-indigo-700">%{comp.share}</span>
</div>
<div className="w-full bg-slate-100 rounded-full h-2 overflow-hidden">
<div className="bg-indigo-600 h-2 rounded-full" style={{ width: `${comp.share}%` }}></div>
</div>
</div>
</div>
))}
</div>
</div>
);
};
+81
View File
@@ -0,0 +1,81 @@
import React from 'react';
import { useApp } from '../../context/AppContext';
import { Newspaper, Calendar, Building2, Package, ArrowRight } from 'lucide-react';
export const DailyBulletin = () => {
const { tenders, setActiveTenderDetail } = useApp();
const todayStr = '10.08.2026';
const todayTenders = tenders.filter(t => t.tarih === todayStr || t.eklenmeZamani.includes('az') || t.eklenmeZamani.includes('önce'));
return (
<div className="space-y-4 my-4 animate-fade-in">
{/* Header */}
<div className="bg-white rounded-xl border border-slate-200 p-5 shadow-xs flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-blue-600 text-white flex items-center justify-center shadow-md">
<Newspaper className="w-6 h-6" />
</div>
<div>
<h1 className="text-xl font-black text-slate-900">Günlük İhale Bülteni</h1>
<p className="text-xs text-slate-500">{todayStr} Tarihinde Yayınlanan Resmi Doğrudan Temin ve Kamu İhaleleri</p>
</div>
</div>
<span className="bg-blue-50 text-blue-800 font-extrabold text-xs px-3 py-1.5 rounded-full border border-blue-200">
{todayTenders.length} Bugünlük İhale Kaydı
</span>
</div>
{/* Bulletin Group List */}
<div className="space-y-4">
{todayTenders.map(t => (
<div key={t.id} className="bg-white rounded-xl border border-slate-200 p-5 shadow-xs hover:shadow-md transition-all space-y-3">
<div className="flex items-start justify-between border-b border-slate-100 pb-3">
<div>
<span className="text-[10px] font-extrabold bg-blue-100 text-blue-800 px-2 py-0.5 rounded">
#{t.ikn}
</span>
<h3
onClick={() => setActiveTenderDetail(t)}
className="font-bold text-base text-slate-900 hover:text-blue-600 cursor-pointer mt-1"
>
{t.baslik}
</h3>
</div>
<span className="text-xs font-bold text-slate-500 bg-slate-100 px-2.5 py-1 rounded-md">
⏰ {t.saat}
</span>
</div>
<div className="flex items-center gap-4 text-xs text-slate-600">
<span className="flex items-center gap-1 font-semibold">
<Building2 className="w-4 h-4 text-slate-400" /> {t.kurum}
</span>
<span className="font-bold text-blue-700">📍 {t.sehir}</span>
</div>
<div className="bg-slate-50 p-3 rounded-lg border border-slate-200 text-xs space-y-1">
<span className="font-bold text-slate-700 block mb-1">Bülten Kalem Özeti:</span>
{t.items?.map((item, i) => (
<div key={i} className="flex justify-between font-semibold text-slate-700">
<span>📦 {item.ad}</span>
<span className="font-mono text-slate-900">{item.miktar} {item.birim}</span>
</div>
))}
</div>
<button
onClick={() => setActiveTenderDetail(t)}
className="text-xs font-bold text-blue-600 hover:text-blue-800 flex items-center gap-1 hover:underline"
>
<span>İhale Detayını Göster</span>
<ArrowRight className="w-4 h-4" />
</button>
</div>
))}
</div>
</div>
);
};
+147
View File
@@ -0,0 +1,147 @@
import React, { useState } from 'react';
import { useApp } from '../../context/AppContext';
import { FolderKanban, FileText, Upload, Plus, ShieldCheck, AlertCircle, CheckCircle2, Download, Trash2, Tag, Calendar } from 'lucide-react';
import confetti from 'canvas-confetti';
export const DocumentVault = () => {
const { vaultDocuments, setVaultDocuments, userProfile } = useApp();
const [docName, setDocName] = useState('');
const [category, setCategory] = useState('Kalite Belgeleri');
const handleAddDocument = (e) => {
e.preventDefault();
if (!docName.trim()) return;
const newDoc = {
id: 'doc-' + Date.now(),
name: docName.endsWith('.pdf') ? docName : docName + '.pdf',
category,
size: (Math.random() * 2 + 0.5).toFixed(1) + ' MB',
updatedAt: new Date().toISOString().split('T')[0],
expiresAt: '2027-12-31',
status: 'Geçerli'
};
setVaultDocuments(prev => [newDoc, ...prev]);
setDocName('');
confetti({ particleCount: 60 });
};
const deleteDoc = (id) => {
setVaultDocuments(prev => prev.filter(d => d.id !== id));
};
return (
<div className="space-y-6 animate-fade-in">
{/* Header */}
<div className="bg-linear-to-r from-slate-900 via-blue-950 to-indigo-900 text-white p-6 rounded-2xl shadow-lg border border-blue-800/40 flex flex-col md:flex-row items-start md:items-center justify-between gap-4">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-2xl bg-blue-600 text-white flex items-center justify-center font-black shadow-md">
<FolderKanban className="w-6 h-6" />
</div>
<div>
<div className="flex items-center gap-2">
<h2 className="font-extrabold text-xl">Doküman ve Teklif Deposu</h2>
<span className="bg-emerald-500 text-slate-950 text-[10px] font-extrabold px-2.5 py-0.5 rounded-full uppercase tracking-wider">
Resmi Evrak Kütüphanesi
</span>
</div>
<p className="text-xs text-blue-200 mt-0.5">Sık kullanılan resmi belgelerinizi (İmza sirküleri, ISO, ÜTS, Faaliyet Belgesi) tek yerde saklayın; ihale teklif paketine saniyeler içinde ekleyin.</p>
</div>
</div>
</div>
{/* Add Document Form */}
<form onSubmit={handleAddDocument} className="bg-white p-5 rounded-2xl border border-slate-200 shadow-xs flex flex-col md:flex-row gap-3 items-end">
<div className="flex-1 space-y-1.5 w-full">
<label className="block text-xs font-bold text-slate-800 uppercase tracking-wider">Yeni Doküman Adı</label>
<input
type="text"
required
value={docName}
onChange={(e) => setDocName(e.target.value)}
placeholder="Örn: 2026 ISO 9001 Kalite Belgesi.pdf"
className="w-full bg-slate-50 border border-slate-300 rounded-xl px-4 py-2.5 text-xs font-semibold text-slate-900 focus:ring-2 focus:ring-blue-500"
/>
</div>
<div className="w-full md:w-64 space-y-1.5">
<label className="block text-xs font-bold text-slate-800 uppercase tracking-wider">Kategori</label>
<select
value={category}
onChange={(e) => setCategory(e.target.value)}
className="w-full bg-slate-50 border border-slate-300 rounded-xl px-3 py-2.5 text-xs font-semibold text-slate-900"
>
<option>Yasal Belgeler</option>
<option>Kalite Belgeleri</option>
<option>Tıbbi Cihaz Kaydı</option>
<option>Resmi Kayıtlar</option>
<option>Mali Belgeler</option>
</select>
</div>
<button
type="submit"
className="w-full md:w-auto bg-blue-600 hover:bg-blue-500 text-white font-extrabold text-xs px-6 py-3 rounded-xl flex items-center justify-center gap-2 shadow-md transition-colors shrink-0"
>
<Upload className="w-4 h-4" />
<span>Depoya Ekle</span>
</button>
</form>
{/* Documents Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{vaultDocuments.map((doc) => (
<div key={doc.id} className="bg-white p-5 rounded-2xl border border-slate-200 shadow-xs hover:shadow-md transition-all space-y-3 flex flex-col justify-between">
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="bg-slate-100 text-slate-700 font-bold text-[10px] px-2.5 py-1 rounded-lg border border-slate-200 flex items-center gap-1">
<Tag className="w-3 h-3 text-blue-600" /> {doc.category}
</span>
<span className="text-[10px] font-bold text-emerald-700 bg-emerald-50 border border-emerald-200 px-2 py-0.5 rounded-full flex items-center gap-1">
<ShieldCheck className="w-3 h-3" /> {doc.status}
</span>
</div>
<div className="flex items-start gap-3 pt-1">
<div className="w-10 h-10 rounded-xl bg-red-50 text-red-600 border border-red-200 flex items-center justify-center shrink-0 font-black">
<FileText className="w-5 h-5" />
</div>
<div>
<h4 className="font-bold text-xs text-slate-900 leading-snug line-clamp-2">{doc.name}</h4>
<p className="text-[10px] text-slate-400 mt-0.5">Boyut: {doc.size}</p>
</div>
</div>
</div>
<div className="border-t border-slate-100 pt-3 flex items-center justify-between text-[11px] text-slate-500 font-medium">
<span className="flex items-center gap-1 text-slate-600">
<Calendar className="w-3.5 h-3.5 text-slate-400" /> Son Geçerlilik: <strong>{doc.expiresAt}</strong>
</span>
<div className="flex items-center gap-1">
<button
onClick={() => alert(`${doc.name} başarıyla indirildi.`)}
className="p-1.5 text-slate-600 hover:text-blue-600 rounded-lg hover:bg-slate-100 transition-colors"
title="İndir"
>
<Download className="w-4 h-4" />
</button>
<button
onClick={() => deleteDoc(doc.id)}
className="p-1.5 text-slate-400 hover:text-red-600 rounded-lg hover:bg-red-50 transition-colors"
title="Sil"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
</div>
</div>
))}
</div>
</div>
);
};
+130
View File
@@ -0,0 +1,130 @@
import React, { useState } from 'react';
import { ResponsiveContainer, LineChart, Line, XAxis, YAxis, Tooltip, CartesianGrid } from 'recharts';
import { TrendingUp, Search, Download, Sparkles, AlertCircle } from 'lucide-react';
import confetti from 'canvas-confetti';
export const PriceAnalysis = () => {
const [selectedProduct, setSelectedProduct] = useState('35*50 TUVAL');
const priceHistoryData = [
{ month: 'Ocak 2026', minFiyat: 110, ortFiyat: 125, maksFiyat: 140 },
{ month: 'Şubat 2026', minFiyat: 115, ortFiyat: 130, maksFiyat: 145 },
{ month: 'Mart 2026', minFiyat: 120, ortFiyat: 135, maksFiyat: 155 },
{ month: 'Nisan 2026', minFiyat: 125, ortFiyat: 140, maksFiyat: 160 },
{ month: 'Mayıs 2026', minFiyat: 130, ortFiyat: 145, maksFiyat: 170 },
{ month: 'Haziran 2026', minFiyat: 135, ortFiyat: 150, maksFiyat: 180 },
{ month: 'Temmuz 2026', minFiyat: 140, ortFiyat: 155, maksFiyat: 185 },
{ month: 'Ağustos 2026', minFiyat: 145, ortFiyat: 160, maksFiyat: 190 }
];
const productsList = [
'35*50 TUVAL',
'50*70 TUVAL',
'1 Gözlü Yemek Kabı',
'Kablosuz Panik Butonu',
'G4 Panel Filtre',
'Çamaşır Suyu 20 L',
'Revizyon Kalça Protez Femoral Stem'
];
const handleDownloadReport = () => {
confetti({ particleCount: 70 });
alert(`${selectedProduct} Fiyat Trend Analizi Raporu (PDF/CSV) İndirildi.`);
};
return (
<div className="space-y-4 my-4 animate-fade-in">
{/* Top Banner */}
<div className="bg-white rounded-xl border border-slate-200 p-5 shadow-xs flex flex-col md:flex-row items-center justify-between gap-4">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-emerald-600 text-white flex items-center justify-center shadow-md">
<TrendingUp className="w-6 h-6" />
</div>
<div>
<h1 className="text-xl font-black text-slate-900">Birim Fiyat Analizi & Trend Grafikleri</h1>
<p className="text-xs text-slate-500">Ürün bazında geçmiş ihalelerdeki birim fiyat trendini ve ortalama teklif marjlarını inceleyin.</p>
</div>
</div>
<button
onClick={handleDownloadReport}
className="bg-emerald-600 hover:bg-emerald-500 text-white font-bold text-xs px-4 py-2 rounded-lg flex items-center gap-2 shadow-xs shrink-0"
>
<Download className="w-4 h-4" /> Rapor İndir (PDF/CSV)
</button>
</div>
{/* Main Grid */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Left Column: Product Selector */}
<div className="bg-white rounded-xl border border-slate-200 p-5 shadow-xs space-y-4 h-fit">
<h3 className="font-bold text-xs uppercase tracking-wider text-slate-800 border-b border-slate-100 pb-2">
Ürün / Malzeme Seçimi
</h3>
<div className="space-y-1.5">
{productsList.map(prod => (
<button
key={prod}
onClick={() => setSelectedProduct(prod)}
className={`w-full text-left p-3 rounded-lg text-xs font-semibold transition-all ${
selectedProduct === prod
? 'bg-emerald-600 text-white shadow-xs'
: 'bg-slate-50 hover:bg-slate-100 text-slate-700 border border-slate-200'
}`}
>
📦 {prod}
</button>
))}
</div>
</div>
{/* Right 2 Columns: Recharts Chart & AI Assistant */}
<div className="lg:col-span-2 space-y-6">
{/* Recharts Line Chart */}
<div className="bg-white rounded-xl border border-slate-200 p-5 shadow-xs space-y-4">
<div className="flex items-center justify-between">
<h3 className="font-bold text-sm text-slate-900">
{selectedProduct} — 8 Aylık Birim Fiyat Değişim Grafiği (TL)
</h3>
<span className="text-[10px] bg-emerald-100 text-emerald-800 font-extrabold px-2 py-0.5 rounded-full">
Canlı EKAP Verisi
</span>
</div>
<div className="h-72 w-full pt-2">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={priceHistoryData}>
<CartesianGrid strokeDasharray="3 3" stroke="#f1f5f9" />
<XAxis dataKey="month" tick={{ fontSize: 11 }} />
<YAxis tick={{ fontSize: 11 }} />
<Tooltip />
<Line type="monotone" dataKey="ortFiyat" stroke="#059669" strokeWidth={3} name="Ortalama Fiyat" />
<Line type="monotone" dataKey="minFiyat" stroke="#2563eb" strokeWidth={2} strokeDasharray="5 5" name="En Düşük Fiyat" />
<Line type="monotone" dataKey="maksFiyat" stroke="#dc2626" strokeWidth={2} strokeDasharray="5 5" name="En Yüksek Fiyat" />
</LineChart>
</ResponsiveContainer>
</div>
</div>
{/* AI Pricing Advice Box */}
<div className="bg-linear-to-r from-slate-900 to-emerald-950 text-white p-5 rounded-xl space-y-3 shadow-md border border-emerald-900/40">
<div className="flex items-center gap-2 font-bold text-sm text-emerald-400">
<Sparkles className="w-5 h-5 text-amber-400" />
<span>Yapay Zeka Teklif Öneri Motoru</span>
</div>
<p className="text-xs text-slate-300 leading-relaxed">
<strong>{selectedProduct}</strong> için son 8 aydaki ihale kazanma verilerine göre optimal teklif birim fiyatınız: <strong className="text-amber-300 text-sm">₺155 - ₺165</strong> aralığıdır. Bu aralıkta kazanma olasılığınız <strong>%88</strong> olarak hesaplanmıştır.
</p>
</div>
</div>
</div>
</div>
);
};
+134
View File
@@ -0,0 +1,134 @@
import React, { useState } from 'react';
import { useApp } from '../../context/AppContext';
import { Smartphone, Wifi, WifiOff, Download, Bell, CheckCircle2, ShieldCheck, Zap, Sparkles } from 'lucide-react';
import confetti from 'canvas-confetti';
export const PwaSettings = () => {
const { userProfile } = useApp();
const [offlineMode, setOfflineMode] = useState(true);
const [pushSubscribed, setPushSubscribed] = useState(true);
const [installed, setInstalled] = useState(false);
const handleInstallPwa = () => {
setInstalled(true);
confetti({ particleCount: 70 });
};
return (
<div className="space-y-6 animate-fade-in">
{/* Header Banner */}
<div className="bg-linear-to-r from-slate-900 via-blue-950 to-cyan-900 text-white p-6 rounded-2xl shadow-lg border border-cyan-800/40 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-2xl bg-cyan-600 text-white flex items-center justify-center font-black shadow-md">
<Smartphone className="w-6 h-6" />
</div>
<div>
<h2 className="font-extrabold text-xl">Mobil PWA & Saha Ekibi Offline Modu</h2>
<p className="text-xs text-cyan-200 mt-0.5">Saha satış temsilcileriniz için internet bağlantısı olmadığında dahi çevrimdışı çalışma ve anlık push bildirimi.</p>
</div>
</div>
</div>
{/* Main Controls Grid */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{/* Offline Cache Status */}
<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">
<h3 className="font-extrabold text-sm text-slate-900 flex items-center gap-2">
{offlineMode ? <Wifi className="w-4 h-4 text-emerald-600" /> : <WifiOff className="w-4 h-4 text-slate-400" />} Çevrimdışı (Offline) Önbellek
</h3>
<span className="bg-emerald-100 text-emerald-800 font-extrabold text-[10px] px-2 py-0.5 rounded-full">
Senkronize
</span>
</div>
<div className="space-y-2 text-xs text-slate-700">
<p className="flex justify-between">
<span>İhale Kayıtları:</span>
<strong className="text-slate-900">102 İhale Önbellekte</strong>
</p>
<p className="flex justify-between">
<span>Şartname PDF'leri:</span>
<strong className="text-slate-900">18 Belge Çevrimdışı</strong>
</p>
<p className="flex justify-between">
<span>Son Senkronizasyon:</span>
<strong className="text-emerald-700">Bugün 14:45</strong>
</p>
</div>
<button
onClick={() => setOfflineMode(!offlineMode)}
className="w-full bg-slate-100 hover:bg-slate-200 text-slate-800 font-bold text-xs py-2.5 rounded-xl border border-slate-200 transition-colors"
>
{offlineMode ? 'Çevrimdışı Önbelleği Yenile' : 'Çevrimdışı Modu Aktif Et'}
</button>
</div>
{/* Mobile Push Notifications */}
<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">
<h3 className="font-extrabold text-sm text-slate-900 flex items-center gap-2">
<Bell className="w-4 h-4 text-amber-500" /> Anlık Mobil Push Bildirimleri
</h3>
<span className="bg-amber-100 text-amber-800 font-extrabold text-[10px] px-2 py-0.5 rounded-full">
İzin Verildi
</span>
</div>
<p className="text-xs text-slate-600 leading-relaxed">
İhale son başvuru saatine 3 saat kala mobil cihazınıza sesli ve titreşimli anlık uyarı bildirimi gönderilir.
</p>
<label className="flex items-center justify-between p-3 bg-slate-50 rounded-xl border border-slate-200 cursor-pointer text-xs font-bold text-slate-800">
<span>Mobil Push Uyarısı</span>
<input
type="checkbox"
checked={pushSubscribed}
onChange={() => setPushSubscribed(!pushSubscribed)}
className="w-4 h-4 accent-cyan-600 rounded"
/>
</label>
</div>
{/* Install PWA Application */}
<div className="bg-linear-to-br from-slate-900 to-cyan-950 text-white p-6 rounded-2xl shadow-xs space-y-4 border border-cyan-800/40">
<div className="flex items-center justify-between">
<h3 className="font-black text-sm text-white flex items-center gap-2">
<Download className="w-4 h-4 text-cyan-400" /> İhaleist Mobil / Masaüstü
</h3>
<span className="bg-cyan-500 text-slate-950 font-black text-[10px] px-2 py-0.5 rounded-full">
PWA 2.0
</span>
</div>
<p className="text-xs text-slate-300 leading-relaxed">
İhaleist'i iOS, Android, Windows veya Mac cihazınıza bir tıklama ile uygulama olarak kurun.
</p>
<button
onClick={handleInstallPwa}
disabled={installed}
className="w-full bg-cyan-500 hover:bg-cyan-400 disabled:bg-slate-700 text-slate-950 font-black text-xs py-3 rounded-xl flex items-center justify-center gap-2 shadow-md transition-colors cursor-pointer"
>
{installed ? (
<>
<CheckCircle2 className="w-4 h-4 text-emerald-950" />
<span>Cihaza Yüklendi!</span>
</>
) : (
<>
<Download className="w-4 h-4 text-slate-950" />
<span>İhaleist Uygulamasını Cihaza Yükle</span>
</>
)}
</button>
</div>
</div>
</div>
);
};
+77
View File
@@ -0,0 +1,77 @@
import React, { useState } from 'react';
import { useApp } from '../../context/AppContext';
import { Trophy, Search, Building2, CheckCircle2, Award } from 'lucide-react';
export const ResultSearch = () => {
const { tenders } = useApp();
const [query, setQuery] = useState('');
const completedTenders = tenders.filter(t => t.durum === 'Sonuçlandı' || t.kazananFirma);
const filteredResults = completedTenders.filter(t => {
if (!query) return true;
const q = query.toLowerCase();
return (
t.baslik.toLowerCase().includes(q) ||
t.ikn.toLowerCase().includes(q) ||
t.kurum.toLowerCase().includes(q) ||
(t.kazananFirma && t.kazananFirma.toLowerCase().includes(q))
);
});
return (
<div className="space-y-4 my-4 animate-fade-in">
<div className="bg-white rounded-xl border border-slate-200 p-5 shadow-xs flex flex-col md:flex-row items-center justify-between gap-4">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-amber-500 text-white flex items-center justify-center shadow-md">
<Trophy className="w-6 h-6" />
</div>
<div>
<h1 className="text-xl font-black text-slate-900">İhale Sonuç Sorgulama</h1>
<p className="text-xs text-slate-500">Sonuçlanmış ihaleleri, kazanan firmaları ve sözleşme bedellerini sorgulayın.</p>
</div>
</div>
<div className="relative w-full md:w-80">
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="İKN, kurum veya kazanan firma ara..."
className="w-full bg-slate-50 border border-slate-300 rounded-lg pl-9 pr-4 py-2 text-xs font-semibold"
/>
<Search className="w-4 h-4 text-slate-400 absolute left-3 top-1/2 -translate-y-1/2" />
</div>
</div>
<div className="space-y-3">
{filteredResults.map(t => (
<div key={t.id} className="bg-white rounded-xl border border-slate-200 p-4 shadow-xs flex flex-col md:flex-row items-center justify-between gap-4">
<div className="space-y-1 flex-1">
<div className="flex items-center gap-2">
<span className="text-[10px] font-extrabold bg-amber-100 text-amber-900 px-2 py-0.5 rounded">
#{t.ikn}
</span>
<span className="text-xs text-slate-500 font-semibold">{t.tarih}</span>
</div>
<h3 className="font-bold text-sm text-slate-900">{t.baslik}</h3>
<p className="text-xs text-slate-500 flex items-center gap-1">
<Building2 className="w-3.5 h-3.5 text-slate-400" /> {t.kurum} ({t.sehir})
</p>
</div>
<div className="bg-emerald-50 border border-emerald-200 p-3 rounded-xl text-right shrink-0 w-full md:w-64">
<span className="text-[10px] font-bold text-emerald-700 block uppercase tracking-wider">KAZANAN FİRMA</span>
<p className="font-extrabold text-xs text-emerald-950 truncate">{t.kazananFirma || 'Tanrıverdi Medikal Ltd.'}</p>
<p className="font-black text-sm text-emerald-700 mt-0.5">
₺{(t.sozlesmeBedeli || 57400).toLocaleString('tr-TR')}
</p>
</div>
</div>
))}
</div>
</div>
);
};
+87
View File
@@ -0,0 +1,87 @@
import React, { useState } from 'react';
import { useApp } from '../../context/AppContext';
import { CalendarDays, Clock, MapPin, AlertCircle } from 'lucide-react';
export const TenderCalendar = () => {
const { tenders, setActiveTenderDetail } = useApp();
const [selectedDay, setSelectedDay] = useState('10.08.2026');
const daysList = [
{ date: '10.08.2026', label: 'Bugün (Pazartesi)', count: 3 },
{ date: '11.08.2026', label: 'Salı', count: 4 },
{ date: '12.08.2026', label: 'Çarşamba', count: 2 },
{ date: '13.08.2026', label: 'Perşembe', count: 5 },
{ date: '14.08.2026', label: 'Cuma', count: 3 },
{ date: '15.08.2026', label: 'Cumartesi', count: 1 }
];
const dayTenders = tenders.filter(t => t.tarih === selectedDay || (selectedDay === '10.08.2026' && t.tarih.includes('10')));
return (
<div className="space-y-4 my-4 animate-fade-in">
{/* Top Banner */}
<div className="bg-white rounded-xl border border-slate-200 p-5 shadow-xs flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-purple-600 text-white flex items-center justify-center shadow-md">
<CalendarDays className="w-6 h-6" />
</div>
<div>
<h1 className="text-xl font-black text-slate-900">7 Günlük İhale Takvimi</h1>
<p className="text-xs text-slate-500">Önümüzdeki 7 gün içerisinde kapanacak ihaleleri günü gününe takip edin.</p>
</div>
</div>
</div>
{/* Day Selector Buttons */}
<div className="flex items-center gap-2 overflow-x-auto pb-1">
{daysList.map(d => (
<button
key={d.date}
onClick={() => setSelectedDay(d.date)}
className={`flex-1 min-w-[130px] p-3 rounded-xl border text-center transition-all cursor-pointer ${
selectedDay === d.date
? 'bg-purple-600 text-white border-purple-700 shadow-sm font-bold'
: 'bg-white text-slate-700 border-slate-200 hover:bg-slate-50 font-semibold'
}`}
>
<span className="text-[11px] block text-purple-200 opacity-90">{d.label}</span>
<span className="text-sm font-black block mt-0.5">{d.date}</span>
<span className={`text-[10px] mt-1 inline-block px-2 py-0.5 rounded-full ${
selectedDay === d.date ? 'bg-white/20 text-white font-extrabold' : 'bg-purple-100 text-purple-800 font-bold'
}`}>
{d.count} İhale Kapanıyor
</span>
</button>
))}
</div>
{/* Tenders List for Selected Day */}
<div className="space-y-3">
{dayTenders.map(t => (
<div
key={t.id}
onClick={() => setActiveTenderDetail(t)}
className="bg-white rounded-xl border border-slate-200 p-4 shadow-xs hover:shadow-md cursor-pointer transition-all flex items-center justify-between gap-4"
>
<div className="space-y-1">
<div className="flex items-center gap-2">
<span className="bg-purple-100 text-purple-900 text-[10px] font-extrabold px-2 py-0.5 rounded">
⏰ {t.saat}
</span>
<span className="text-xs text-slate-400 font-mono">#{t.ikn}</span>
</div>
<h3 className="font-bold text-sm text-slate-900 hover:text-purple-600">{t.baslik}</h3>
<p className="text-xs text-slate-500 font-medium">🏢 {t.kurum} • 📍 {t.sehir}</p>
</div>
<span className="bg-amber-500 text-white font-extrabold text-xs px-3 py-1.5 rounded-lg shrink-0">
{t.alimTuru}
</span>
</div>
))}
</div>
</div>
);
};
+177
View File
@@ -0,0 +1,177 @@
import React, { useState } from 'react';
import { Calculator, Copy, Check } from 'lucide-react';
export const VatCalculator = () => {
const [mode, setMode] = useState('haric'); // 'haric' (net to gross) or 'dahil' (gross to net)
const [amount, setAmount] = useState(100000);
const [vatRate, setVatRate] = useState(20); // 1, 10, 20
const [tevkifatRate, setTevkifatRate] = useState(0); // 0, 50 (5/10), 70 (7/10), 90 (9/10)
const [copied, setCopied] = useState(false);
// Calculations
let netAmount = 0;
let vatAmount = 0;
let grossAmount = 0;
let tevkifatAmount = 0;
let payableVat = 0;
if (mode === 'haric') {
netAmount = Number(amount) || 0;
vatAmount = netAmount * (vatRate / 100);
grossAmount = netAmount + vatAmount;
} else {
grossAmount = Number(amount) || 0;
netAmount = grossAmount / (1 + vatRate / 100);
vatAmount = grossAmount - netAmount;
}
if (tevkifatRate > 0) {
tevkifatAmount = vatAmount * (tevkifatRate / 100);
payableVat = vatAmount - tevkifatAmount;
} else {
payableVat = vatAmount;
}
const handleCopy = () => {
const summary = `Matrah: ₺${netAmount.toLocaleString('tr-TR', { maximumFractionDigits: 2 })} | KDV (%${vatRate}): ₺${vatAmount.toLocaleString('tr-TR', { maximumFractionDigits: 2 })} | Genel Toplam: ₺${grossAmount.toLocaleString('tr-TR', { maximumFractionDigits: 2 })}`;
navigator.clipboard.writeText(summary);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<div className="max-w-3xl mx-auto my-4 space-y-4 animate-fade-in">
<div className="bg-white rounded-xl border border-slate-200 p-5 shadow-xs flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-blue-600 text-white flex items-center justify-center shadow-md">
<Calculator className="w-6 h-6" />
</div>
<div>
<h1 className="text-xl font-black text-slate-900">İhale KDV & Tevkifat Hesaplayıcı</h1>
<p className="text-xs text-slate-500">Teklif matrahından KDV dahil tutarı veya KDV dahilden net matrahı hesaplayın.</p>
</div>
</div>
</div>
<div className="bg-white rounded-2xl border border-slate-200 p-6 shadow-xs space-y-6">
{/* Mode Selector */}
<div className="grid grid-cols-2 gap-3 p-1 bg-slate-100 rounded-xl">
<button
onClick={() => setMode('haric')}
className={`py-2.5 rounded-lg text-xs font-extrabold transition-all ${
mode === 'haric' ? 'bg-white text-blue-700 shadow-2xs' : 'text-slate-600 hover:text-slate-900'
}`}
>
KDV Hariç ➔ KDV Dahil Hesapla
</button>
<button
onClick={() => setMode('dahil')}
className={`py-2.5 rounded-lg text-xs font-extrabold transition-all ${
mode === 'dahil' ? 'bg-white text-blue-700 shadow-2xs' : 'text-slate-600 hover:text-slate-900'
}`}
>
KDV Dahil ➔ Matrah Hesapla
</button>
</div>
{/* Inputs */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-xs">
<div>
<label className="block font-bold text-slate-700 mb-1.5">
{mode === 'haric' ? 'Teklif Tutarı (KDV Hariç Matrah)' : 'KDV Dahil Toplam Tutar'}
</label>
<div className="relative">
<input
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
className="w-full bg-slate-50 border border-slate-300 rounded-lg px-3 py-2.5 font-mono text-sm font-bold text-slate-900 focus:ring-2 focus:ring-blue-500"
/>
<span className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 font-bold">TL</span>
</div>
</div>
<div>
<label className="block font-bold text-slate-700 mb-1.5">KDV Oranı</label>
<div className="flex gap-1.5">
{[1, 10, 20].map(rate => (
<button
key={rate}
type="button"
onClick={() => setVatRate(rate)}
className={`flex-1 py-2 rounded-lg font-extrabold transition-colors border ${
vatRate === rate ? 'bg-blue-600 text-white border-blue-600' : 'bg-slate-50 text-slate-700 border-slate-300 hover:bg-slate-100'
}`}
>
%{rate}
</button>
))}
</div>
</div>
<div>
<label className="block font-bold text-slate-700 mb-1.5">Tevkifat Oranı (Varsa)</label>
<select
value={tevkifatRate}
onChange={(e) => setTevkifatRate(Number(e.target.value))}
className="w-full bg-slate-50 border border-slate-300 rounded-lg px-3 py-2.5 font-semibold text-slate-800"
>
<option value={0}>Tevkifat Yok</option>
<option value={50}>5/10 Tevkifat (%50)</option>
<option value={70}>7/10 Tevkifat (%70)</option>
<option value={90}>9/10 Tevkifat (%90)</option>
</select>
</div>
</div>
{/* Results Card */}
<div className="bg-slate-900 text-white p-6 rounded-xl space-y-4 shadow-lg">
<div className="flex items-center justify-between border-b border-slate-800 pb-3">
<span className="font-bold text-xs uppercase tracking-wider text-slate-300">Hesaplama Sonucu</span>
<button
onClick={handleCopy}
className="bg-slate-800 hover:bg-slate-700 text-blue-400 font-semibold px-3 py-1.5 rounded-lg text-xs flex items-center gap-1.5 transition-colors border border-slate-700"
>
{copied ? <Check className="w-3.5 h-3.5 text-emerald-400" /> : <Copy className="w-3.5 h-3.5" />}
<span>{copied ? 'Kopyalandı!' : 'Kopyala'}</span>
</button>
</div>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 text-xs">
<div className="bg-white/5 p-3 rounded-lg border border-white/10">
<span className="text-slate-400 font-medium block">Net Matrah (KDV Hariç)</span>
<span className="text-lg font-black text-white mt-1 block">
₺{netAmount.toLocaleString('tr-TR', { maximumFractionDigits: 2 })}
</span>
</div>
<div className="bg-white/5 p-3 rounded-lg border border-white/10">
<span className="text-slate-400 font-medium block">Hesaplanan KDV (%{vatRate})</span>
<span className="text-lg font-black text-amber-400 mt-1 block">
₺{vatAmount.toLocaleString('tr-TR', { maximumFractionDigits: 2 })}
</span>
</div>
<div className="bg-white/5 p-3 rounded-lg border border-white/10">
<span className="text-slate-400 font-medium block">Genel Toplam (KDV Dahil)</span>
<span className="text-xl font-black text-emerald-400 mt-1 block">
₺{grossAmount.toLocaleString('tr-TR', { maximumFractionDigits: 2 })}
</span>
</div>
</div>
{tevkifatRate > 0 && (
<div className="bg-blue-950/80 p-3.5 rounded-lg border border-blue-800 text-xs flex justify-between">
<span className="text-slate-300">Tevkifat Tutarı: <strong className="text-amber-300">₺{tevkifatAmount.toLocaleString('tr-TR', { maximumFractionDigits: 2 })}</strong></span>
<span className="text-slate-300">Ödenecek KDV: <strong className="text-emerald-400">₺{payableVat.toLocaleString('tr-TR', { maximumFractionDigits: 2 })}</strong></span>
</div>
)}
</div>
</div>
</div>
);
};