- /mcp (JSON-RPC, ih_live_ anahtarı): search_tenders, get_tender, search_results, search_items, upcoming_deadlines - /api/feeds/tenders.rss (anahtarlı) ve /api/feeds/sonuc.rss (açık) - Haftalık brief (Pazartesi 08:00, Telegram + e-posta), /api/me/brief tercihi + önizleme, sağ panelde yaklaşan ihale tarihleri - Detayda Teklif Asistanı sekmesi: belgeye sor (atıflı), taslak denetimi, taslak üretimi (analyzeSpec zinciri) - Derin analiz belge listesi × Doküman Deposu eşleştirme rozeti - Kırmızı bayrak: kısa süre, hafta sonu tarih, geç sözleşme, kazananı gizli, DT parçalama şüphesi - Kullanım kılavuzu güncellendi Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
584 lines
30 KiB
React
584 lines
30 KiB
React
import React, { useState } from 'react';
|
||
import { useApp } from '../context/AppContext';
|
||
import { TenderDocuments } from './TenderDocuments';
|
||
import { TeklifAsistani } from './TeklifAsistani';
|
||
import { TeklifCetveliKalemleri } from './TeklifCetveliKalemleri';
|
||
import {
|
||
X, Star, Share2, Calendar, MapPin, Building2, Tag, FileText, CheckSquare,
|
||
DollarSign, Calculator, Download, Sparkles, AlertTriangle, ShieldCheck,
|
||
Send, Stamp, ArrowLeft, ExternalLink, Printer, PenLine } from 'lucide-react';
|
||
import confetti from 'canvas-confetti';
|
||
|
||
export const TenderDetailModal = () => {
|
||
const { activeTenderDetail, setActiveTenderDetail, toggleTrackTender, userProfile, setIsAiSpecAnalyzerOpen, gorevAc, gorevKontrol, aiAnalysisResults } = useApp();
|
||
const [activeTab, setActiveTab] = useState('details'); // 'details', 'pdf_stamp', 'ai_assistant', 'checklist', 'kurum_tenders'
|
||
const [noteText, setNoteText] = useState('');
|
||
const [savedNotes, setSavedNotes] = useState([
|
||
{ id: 1, author: 'Hikmet Tanrıverdi', text: 'Kayseri deposuna doğrudan sevkiyat yapılacak. Birim fiyat 145 TL üstü olmalı.', date: 'Bugün 10:15' }
|
||
]);
|
||
|
||
// Kur Farkı Calculator State
|
||
const [bidAmount, setBidAmount] = useState(15000);
|
||
const usdRate = 36.45;
|
||
const eurRate = 39.80;
|
||
|
||
// Stamp Drag Position State
|
||
const [stampPos, setStampPos] = useState({ x: 220, y: 180 });
|
||
const [isDraggingStamp, setIsDraggingStamp] = useState(false);
|
||
|
||
if (!activeTenderDetail) return null;
|
||
const tender = activeTenderDetail;
|
||
|
||
const handleAddNote = (e) => {
|
||
e.preventDefault();
|
||
if (!noteText.trim()) return;
|
||
setSavedNotes([...savedNotes, {
|
||
id: Date.now(),
|
||
author: userProfile.name,
|
||
text: noteText.trim(),
|
||
date: 'Az önce'
|
||
}]);
|
||
setNoteText('');
|
||
};
|
||
|
||
const handleExportICS = () => {
|
||
confetti({ particleCount: 50 });
|
||
const icsData = `BEGIN:VCALENDAR
|
||
VERSION:2.0
|
||
PRODID:-//İhaleist//İhale Takvimi//TR
|
||
BEGIN:VEVENT
|
||
SUMMARY:İhale Son Teslim: ${tender.baslik}
|
||
DESCRIPTION:İKN: ${tender.ikn} - ${tender.kurum}
|
||
DTSTART:20260810T120000Z
|
||
DTEND:20260810T130000Z
|
||
END:VEVENT
|
||
END:VCALENDAR`;
|
||
|
||
const blob = new Blob([icsData], { type: 'text/calendar;charset=utf-8' });
|
||
const link = document.createElement('a');
|
||
link.href = window.URL.createObjectURL(blob);
|
||
link.setAttribute('download', `ihale_${tender.ikn}.ics`);
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
document.body.removeChild(link);
|
||
};
|
||
|
||
const handleStampMouseDown = () => setIsDraggingStamp(true);
|
||
const handleStampMouseMove = (e) => {
|
||
if (!isDraggingStamp) return;
|
||
const rect = e.currentTarget.getBoundingClientRect();
|
||
setStampPos({
|
||
x: Math.max(20, Math.min(rect.width - 150, e.clientX - rect.left - 60)),
|
||
y: Math.max(20, Math.min(rect.height - 80, e.clientY - rect.top - 30))
|
||
});
|
||
};
|
||
const handleStampMouseUp = () => setIsDraggingStamp(false);
|
||
|
||
return (
|
||
<div className="fixed inset-0 z-50 overflow-y-auto bg-slate-900/70 backdrop-blur-xs flex justify-center p-2 sm:p-4 animate-fade-in">
|
||
<div className="bg-white w-full max-w-6xl rounded-2xl shadow-2xl border border-slate-200 overflow-hidden my-auto flex flex-col max-h-[92vh]">
|
||
|
||
{/* Top Header Controls */}
|
||
<div className="bg-[#1f2937] text-white px-6 py-3.5 flex items-center justify-between border-b border-slate-700 shrink-0">
|
||
<button
|
||
onClick={() => setActiveTenderDetail(null)}
|
||
className="flex items-center gap-2 text-xs font-semibold text-slate-300 hover:text-white transition-colors bg-slate-800 px-3 py-1.5 rounded-lg border border-slate-700"
|
||
>
|
||
<ArrowLeft className="w-4 h-4" />
|
||
<span>Listeye Geri Dön</span>
|
||
</button>
|
||
|
||
<div className="flex items-center gap-4 text-xs">
|
||
<span className="text-slate-400">İnceleyen: <strong className="text-white">{userProfile.name}</strong></span>
|
||
<button
|
||
onClick={() => toggleTrackTender(tender.id)}
|
||
className={`flex items-center gap-1.5 font-bold px-3 py-1.5 rounded-lg text-xs transition-colors ${
|
||
tender.takipte ? 'bg-amber-400 text-slate-900' : 'bg-slate-700 text-white hover:bg-slate-600'
|
||
}`}
|
||
>
|
||
<Star className={`w-4 h-4 ${tender.takipte ? 'fill-slate-900' : ''}`} />
|
||
<span>{tender.takipte ? 'Takipte' : 'Takip Et'}</span>
|
||
</button>
|
||
{/* İhaleden göreve: teslim tarihi ihale saati; sonuç yayımlanınca bildirim */}
|
||
{tender.gorev ? (
|
||
<span className="flex items-center gap-1.5 font-bold px-3 py-1.5 rounded-lg text-xs bg-emerald-500/20 text-emerald-200 border border-emerald-400/40"
|
||
title={`Hazırlık görevi açık · teslim: ${tender.gorev.teslim || '—'}`}>
|
||
✓ Görev açık{tender.gorev.teslim ? ` · ${tender.gorev.teslim}` : ''}
|
||
</span>
|
||
) : (
|
||
<button
|
||
type="button"
|
||
onClick={() => gorevAc(tender.id, { kontrolListesi: aiAnalysisResults?.documentChecklist || [] })}
|
||
title="Hazırlık görevi aç: teslim tarihi ihale saati olur, pano 'Teklif Hazırlanıyor' kolonuna düşer, sonuç yayımlanınca bildirim gelir"
|
||
className="flex items-center gap-1.5 font-bold px-3 py-1.5 rounded-lg text-xs transition-colors bg-amber-400 text-slate-950 hover:bg-amber-300"
|
||
>
|
||
🗂 Hazırlık Görevi Aç
|
||
</button>
|
||
)}
|
||
<button
|
||
onClick={() => setActiveTenderDetail(null)}
|
||
className="text-slate-400 hover:text-white p-1"
|
||
>
|
||
<X className="w-6 h-6" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Scrollable Container */}
|
||
<div className="overflow-y-auto p-6 space-y-6 flex-1">
|
||
|
||
{/* Main Title Header */}
|
||
<div className="space-y-2 border-b border-slate-200 pb-4">
|
||
<div className="flex items-center gap-2 flex-wrap">
|
||
<span className="bg-amber-500 text-white font-extrabold text-xs px-3 py-1 rounded-md">
|
||
{tender.alimTuru}
|
||
</span>
|
||
<span className="bg-blue-50 text-blue-700 border border-blue-200 font-bold text-xs px-3 py-1 rounded-md">
|
||
İKN: {tender.ikn}
|
||
</span>
|
||
{tender.aciliyetBadge && (
|
||
<span className="bg-red-500 text-white font-bold text-xs px-2.5 py-1 rounded-md">
|
||
{tender.aciliyetBadge}
|
||
</span>
|
||
)}
|
||
<span className="text-xs text-slate-500 ml-auto">Ekleme: {tender.eklenmeZamani}</span>
|
||
</div>
|
||
|
||
<h1 className="text-xl sm:text-2xl font-black text-slate-900 leading-snug">
|
||
{tender.baslik}
|
||
</h1>
|
||
</div>
|
||
|
||
{/* Metadata Cards Grid */}
|
||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
||
<div className="bg-slate-50 p-3 rounded-xl border border-slate-200 text-xs">
|
||
<span className="text-slate-400 font-medium block">Kurum Adı</span>
|
||
<span className="font-bold text-slate-900 truncate block mt-0.5" title={tender.kurum}>{tender.kurum}</span>
|
||
</div>
|
||
<div className="bg-slate-50 p-3 rounded-xl border border-slate-200 text-xs">
|
||
<span className="text-slate-400 font-medium block">İhale Tarihi & Saat</span>
|
||
<span className="font-bold text-blue-700 block mt-0.5">{tender.tarih} - {tender.saat}</span>
|
||
</div>
|
||
<div className="bg-slate-50 p-3 rounded-xl border border-slate-200 text-xs">
|
||
<span className="text-slate-400 font-medium block">Şehir / Bölge</span>
|
||
<span className="font-bold text-slate-900 block mt-0.5">{tender.sehir} ({tender.bolge})</span>
|
||
</div>
|
||
<div className="bg-slate-50 p-3 rounded-xl border border-slate-200 text-xs">
|
||
<span className="text-slate-400 font-medium block">Yaklaşık Maliyet</span>
|
||
<span className="font-bold text-emerald-700 block mt-0.5">
|
||
{tender.yaklasikMaliyet ? `${tender.yaklasikMaliyet.toLocaleString('tr-TR')} TL` : 'Gizli / Belirtilmemiş'}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Navigation Tabs */}
|
||
<div className="flex items-center gap-2 border-b border-slate-200 overflow-x-auto pb-1">
|
||
{[
|
||
{ id: 'details', label: 'İhale Detayları', icon: FileText },
|
||
{ id: 'pdf_stamp', label: 'Birim Fiyat Cetveli (PDF & Kaşe)', icon: Stamp, badge: 'İnteraktif' },
|
||
{ id: 'ai_assistant', label: 'Yapay Zeka Asistanı', icon: Sparkles, badge: '%94 Uyum' },
|
||
{ id: 'checklist', label: 'Hazırlık Checklist', icon: CheckSquare },
|
||
{ id: 'teklif_asistani', label: 'Teklif Asistanı', icon: PenLine, badge: 'AI' },
|
||
].map(tab => {
|
||
const Icon = tab.icon;
|
||
const isActive = activeTab === tab.id;
|
||
return (
|
||
<button
|
||
key={tab.id}
|
||
onClick={() => setActiveTab(tab.id)}
|
||
className={`flex items-center gap-2 px-4 py-2.5 rounded-lg text-xs font-bold transition-all whitespace-nowrap ${
|
||
isActive
|
||
? 'bg-blue-600 text-white shadow-xs'
|
||
: 'text-slate-600 hover:bg-slate-100'
|
||
}`}
|
||
>
|
||
<Icon className="w-4 h-4" />
|
||
<span>{tab.label}</span>
|
||
{tab.badge && (
|
||
<span className={`text-[10px] px-1.5 py-0.5 rounded-full font-extrabold ${
|
||
isActive ? 'bg-white/20 text-white' : 'bg-blue-100 text-blue-800'
|
||
}`}>
|
||
{tab.badge}
|
||
</span>
|
||
)}
|
||
</button>
|
||
);
|
||
})}
|
||
</div>
|
||
|
||
{/* Tab 1: İhale Detayları */}
|
||
{activeTab === 'details' && (
|
||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||
|
||
<div className="lg:col-span-2 space-y-6">
|
||
|
||
{/* Description & Terms */}
|
||
<div className="bg-slate-50 p-5 rounded-xl border border-slate-200 space-y-3">
|
||
<h3 className="font-bold text-sm text-slate-900 flex items-center gap-2">
|
||
<FileText className="w-4 h-4 text-blue-600" /> İhale Açıklaması & Teslimat Koşulları
|
||
</h3>
|
||
<p className="text-xs text-slate-700 leading-relaxed">{tender.aciklama}</p>
|
||
|
||
<div className="grid grid-cols-2 gap-4 pt-2 border-t border-slate-200 text-xs">
|
||
<div>
|
||
<span className="text-slate-400 block font-medium">Teslimat Süresi</span>
|
||
<span className="font-bold text-slate-800">{tender.teslimatSuresi}</span>
|
||
</div>
|
||
<div>
|
||
<span className="text-slate-400 block font-medium">Teslimat Yeri</span>
|
||
<span className="font-bold text-slate-800">{tender.teslimYeri}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{tender.gorev?.kontrolListesi?.length > 0 && (
|
||
<div className="bg-amber-50 border border-amber-200 rounded-xl p-4 space-y-2">
|
||
<h3 className="font-bold text-sm text-slate-900">Hazırlık görevi — belge kontrol listesi</h3>
|
||
<ul className="space-y-1">
|
||
{tender.gorev.kontrolListesi.map((k, i) => (
|
||
<li key={i}>
|
||
<button type="button" onClick={() => gorevKontrol(tender.id, i)}
|
||
className={`text-xs text-left flex items-center gap-2 ${k.tamam ? 'line-through text-slate-400' : 'text-slate-800'}`}>
|
||
<span className={`w-3.5 h-3.5 rounded border ${k.tamam ? 'bg-emerald-500 border-emerald-500' : 'border-slate-400'}`} />
|
||
{k.ad}
|
||
</button>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
)}
|
||
|
||
{/* Ortak havuzdan teklif cetveli kalemleri */}
|
||
<TeklifCetveliKalemleri ikn={tender.ikn} />
|
||
|
||
{/* Material Table */}
|
||
{tender.items?.length > 0 && (
|
||
<div className="space-y-3">
|
||
<h3 className="font-bold text-sm text-slate-900">Malzeme / Kalem Listesi ({tender.items?.length || 0} Kalem)</h3>
|
||
<div className="border border-slate-200 rounded-xl overflow-hidden shadow-2xs">
|
||
<table className="w-full text-left text-xs">
|
||
<thead className="bg-slate-100 text-slate-700 font-bold border-b border-slate-200">
|
||
<tr>
|
||
<th className="p-3">#</th>
|
||
<th className="p-3">Malzeme Adı</th>
|
||
<th className="p-3">Miktar</th>
|
||
<th className="p-3">Tahmini Birim Fiyat</th>
|
||
<th className="p-3">Toplam Tutarlar</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody className="divide-y divide-slate-100">
|
||
{tender.items?.map((item, index) => (
|
||
<tr key={index} className="hover:bg-slate-50">
|
||
<td className="p-3 font-bold text-slate-400">{index + 1}</td>
|
||
<td className="p-3 font-bold text-slate-800">{item.ad}</td>
|
||
<td className="p-3 font-semibold text-slate-700">{item.miktar} {item.birim}</td>
|
||
<td className="p-3 font-mono text-slate-600">{item.birimFiyat ? `${item.birimFiyat} TL` : '—'}</td>
|
||
<td className="p-3 font-mono font-bold text-emerald-700">
|
||
{item.birimFiyat ? `${(item.birimFiyat * parseFloat(item.miktar)).toLocaleString('tr-TR')} TL` : '—'}
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Team Notes Area */}
|
||
<div className="bg-white p-5 rounded-xl border border-slate-200 space-y-4">
|
||
<h3 className="font-bold text-sm text-slate-900">Ekip Notları</h3>
|
||
<div className="space-y-2">
|
||
{savedNotes.map(n => (
|
||
<div key={n.id} className="bg-slate-50 p-3 rounded-lg border border-slate-200 text-xs">
|
||
<div className="flex justify-between font-bold text-slate-900">
|
||
<span>{n.author}</span>
|
||
<span className="text-slate-400 text-[10px] font-normal">{n.date}</span>
|
||
</div>
|
||
<p className="text-slate-700 mt-1">{n.text}</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
<form onSubmit={handleAddNote} className="flex gap-2">
|
||
<input
|
||
type="text"
|
||
value={noteText}
|
||
onChange={(e) => setNoteText(e.target.value)}
|
||
placeholder="Ekip arkadaşlarınıza not yazın..."
|
||
className="flex-1 bg-slate-50 border border-slate-300 rounded-lg px-3 py-2 text-xs"
|
||
/>
|
||
<button type="submit" className="bg-blue-600 hover:bg-blue-500 text-white font-bold px-4 py-2 rounded-lg text-xs flex items-center gap-1">
|
||
<Send className="w-3.5 h-3.5" /> Not Ekle
|
||
</button>
|
||
</form>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
{/* Right Sidebar of Details: TCMB Kur & Calendar & Specs */}
|
||
<div className="space-y-6">
|
||
|
||
{/* TCMB Kur Farkı Calculator */}
|
||
<div className="bg-linear-to-br from-blue-900 to-slate-900 text-white p-5 rounded-xl space-y-4 shadow-md">
|
||
<div className="flex items-center justify-between border-b border-blue-800/80 pb-2">
|
||
<span className="font-bold text-xs uppercase tracking-wider flex items-center gap-1.5">
|
||
<Calculator className="w-4 h-4 text-blue-400" /> Kur Farkı Hesaplayıcı
|
||
</span>
|
||
<span className="text-[10px] bg-blue-500/30 text-blue-300 px-2 py-0.5 rounded font-bold">TCMB Canlı</span>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 gap-2 text-xs font-semibold">
|
||
<div className="bg-white/10 p-2.5 rounded-lg border border-white/10">
|
||
<span className="text-slate-400 block text-[10px]">USD / TRY</span>
|
||
<span className="text-base font-black text-emerald-400">₺{usdRate}</span>
|
||
</div>
|
||
<div className="bg-white/10 p-2.5 rounded-lg border border-white/10">
|
||
<span className="text-slate-400 block text-[10px]">EUR / TRY</span>
|
||
<span className="text-base font-black text-blue-400">₺{eurRate}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="space-y-1">
|
||
<label className="text-[11px] text-slate-300 font-medium">Teklif Tutarı (TL)</label>
|
||
<input
|
||
type="number"
|
||
value={bidAmount}
|
||
onChange={(e) => setBidAmount(Number(e.target.value))}
|
||
className="w-full bg-white/10 border border-white/20 rounded-lg px-3 py-1.5 text-xs text-white"
|
||
/>
|
||
</div>
|
||
|
||
<div className="bg-blue-950/80 p-3 rounded-lg border border-blue-800 text-xs space-y-1">
|
||
<div className="flex justify-between text-slate-300">
|
||
<span>Dolar Karşılığı:</span>
|
||
<span className="font-bold text-white">${(bidAmount / usdRate).toFixed(2)}</span>
|
||
</div>
|
||
<div className="flex justify-between text-slate-300">
|
||
<span>Euro Karşılığı:</span>
|
||
<span className="font-bold text-white">€{(bidAmount / eurRate).toFixed(2)}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Downloads & Calendar */}
|
||
<div className="bg-white p-5 rounded-xl border border-slate-200 space-y-3">
|
||
<h4 className="font-bold text-xs uppercase tracking-wider text-slate-800">İhale Dosyaları & Takvim</h4>
|
||
<button
|
||
onClick={() => setActiveTab('pdf_stamp')}
|
||
className="w-full bg-blue-600 hover:bg-blue-500 text-white font-bold py-2.5 px-3 rounded-lg text-xs flex items-center justify-center gap-2 shadow-2xs"
|
||
>
|
||
<Download className="w-4 h-4" /> Birim Fiyat Cetveli (PDF Aç/Düzenle)
|
||
</button>
|
||
<button
|
||
onClick={handleExportICS}
|
||
className="w-full bg-slate-100 hover:bg-slate-200 text-slate-700 font-bold py-2.5 px-3 rounded-lg text-xs flex items-center justify-center gap-2 border border-slate-300"
|
||
>
|
||
<Calendar className="w-4 h-4 text-blue-600" /> Google / Outlook Takvime Ekle
|
||
</button>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
)}
|
||
|
||
{/* Tab 2: PDF Birim Fiyat Cetveli & Kaşe Editörü */}
|
||
{activeTab === 'pdf_stamp' && (
|
||
<div className="space-y-4">
|
||
<div className="bg-amber-50 border border-amber-200 text-amber-900 p-4 rounded-xl text-xs flex items-center justify-between">
|
||
<div className="flex items-center gap-2 font-semibold">
|
||
<Stamp className="w-5 h-5 text-amber-600" />
|
||
<span>Teklif cetvelinizi doldurun ve Dijital Kaşenizi sürükleyerek belgenin altına yerleştirin.</span>
|
||
</div>
|
||
<button
|
||
onClick={() => {
|
||
import('../services/pdf').then(({ generateBidSheetPDF }) => {
|
||
generateBidSheetPDF(tender, { firma: userProfile.company, stamp: true });
|
||
});
|
||
}}
|
||
className="bg-amber-600 hover:bg-amber-500 text-white font-bold px-4 py-2 rounded-lg flex items-center gap-1.5 shadow-2xs"
|
||
>
|
||
<Download className="w-4 h-4" /> Kaşeli PDF İndir
|
||
</button>
|
||
</div>
|
||
|
||
{/* Interactive PDF Sheet Canvas Simulator */}
|
||
<div
|
||
onMouseMove={handleStampMouseMove}
|
||
onMouseUp={handleStampMouseUp}
|
||
className="bg-slate-200 p-6 rounded-2xl border border-slate-300 relative select-none min-h-[500px] flex justify-center"
|
||
>
|
||
{/* Paper sheet */}
|
||
<div className="bg-white w-full max-w-2xl p-8 rounded-lg shadow-xl border border-slate-300 relative space-y-6 text-slate-800">
|
||
<div className="text-center border-b-2 border-slate-800 pb-4 space-y-1">
|
||
<p className="font-extrabold text-sm uppercase">{tender.kurum}</p>
|
||
<p className="font-bold text-xs text-blue-900">BİRİM FİYAT TEKLİF CETVELİ — İKN: {tender.ikn}</p>
|
||
</div>
|
||
|
||
<table className="w-full text-left text-xs border border-slate-400">
|
||
<thead className="bg-slate-100 font-bold border-b border-slate-400">
|
||
<tr>
|
||
<th className="p-2 border-r border-slate-400">Sıra</th>
|
||
<th className="p-2 border-r border-slate-400">Malzeme Adı</th>
|
||
<th className="p-2 border-r border-slate-400">Miktar</th>
|
||
<th className="p-2 border-r border-slate-400">Birim Fiyat (TL)</th>
|
||
<th className="p-2">Toplam (TL)</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody className="divide-y divide-slate-300">
|
||
{tender.items?.map((item, idx) => (
|
||
<tr key={idx}>
|
||
<td className="p-2 border-r border-slate-400 font-bold text-center">{idx + 1}</td>
|
||
<td className="p-2 border-r border-slate-400 font-semibold">{item.ad}</td>
|
||
<td className="p-2 border-r border-slate-400">{item.miktar} {item.birim}</td>
|
||
<td className="p-2 border-r border-slate-400 bg-yellow-50 font-bold text-blue-900">
|
||
{item.birimFiyat || 150} TL
|
||
</td>
|
||
<td className="p-2 font-mono font-bold text-emerald-800">
|
||
{((item.birimFiyat || 150) * parseFloat(item.miktar)).toLocaleString('tr-TR')} TL
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
|
||
<div className="pt-12 text-right">
|
||
<p className="font-bold text-xs text-slate-800">{userProfile.company}</p>
|
||
<p className="text-[10px] text-slate-500">Yetkili İmza ve Kaşe Alanı</p>
|
||
</div>
|
||
|
||
{/* Draggable Stamp Element */}
|
||
<div
|
||
onMouseDown={handleStampMouseDown}
|
||
style={{ left: `${stampPos.x}px`, top: `${stampPos.y}px` }}
|
||
className="absolute cursor-grab active:cursor-grabbing border-2 border-dashed border-red-500 bg-red-500/10 p-3 rounded-lg text-center transform -rotate-6 shadow-md transition-shadow hover:shadow-lg select-none"
|
||
>
|
||
<div className="w-32 h-16 border-2 border-red-700 rounded flex flex-col items-center justify-center p-1 text-red-700 font-black text-[9px] uppercase tracking-tighter">
|
||
<span>{userProfile.company.slice(0, 25)}</span>
|
||
<span className="text-[8px] text-red-600 font-bold mt-0.5">MÜDÜR İMZA & KAŞE</span>
|
||
<span className="text-[7px] text-slate-500">2026-08-07 ONAYLI</span>
|
||
</div>
|
||
<span className="text-[9px] font-extrabold text-red-600 block mt-1">↕ Sürükleyip Bırakın</span>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Tab 3: AI Asistanı */}
|
||
{activeTab === 'ai_assistant' && (
|
||
<div className="space-y-6">
|
||
<div className="bg-linear-to-r from-blue-900 to-indigo-900 text-white p-6 rounded-2xl space-y-4 shadow-lg">
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center gap-3">
|
||
<Sparkles className="w-6 h-6 text-amber-400" />
|
||
<div>
|
||
<h3 className="font-black text-lg">Yapay Zeka Teklif & Risk Değerlendirmesi</h3>
|
||
<p className="text-xs text-blue-200">Teknik şartname ve geçmiş ihale sonuçları analiz edildi.</p>
|
||
</div>
|
||
</div>
|
||
{tender.aiMatchScore != null && (
|
||
<span className="bg-amber-400 text-slate-900 font-black text-xs px-3 py-1.5 rounded-xl shadow-xs">
|
||
%{tender.aiMatchScore} Şirket Uyumu
|
||
</span>
|
||
)}
|
||
</div>
|
||
{/* Skorun nedeni: hangi profil terimi ihalenin neresinde geçiyor */}
|
||
{tender.uyumNedenleri?.length > 0 && (
|
||
<div className="flex flex-wrap gap-1.5 pt-1">
|
||
{tender.uyumNedenleri.map((n, i) => (
|
||
<span key={i} className="bg-white/10 border border-white/15 text-[11px] text-amber-100 px-2 py-0.5 rounded-lg">
|
||
<strong className="text-white">{n.terim}</strong> · {n.alan}
|
||
</span>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 pt-2">
|
||
<div className="bg-white/10 p-4 rounded-xl border border-white/10 space-y-1">
|
||
<span className="text-slate-300 text-xs font-semibold block">Risk Skoru</span>
|
||
<span className="text-lg font-black text-emerald-400 flex items-center gap-1">
|
||
<ShieldCheck className="w-5 h-5" /> {tender.aiRiskScore || 'Analiz Bekliyor'}{tender.aiRiskScore ? ' Risk' : ''}
|
||
</span>
|
||
</div>
|
||
<div className="bg-white/10 p-4 rounded-xl border border-white/10 space-y-1">
|
||
<span className="text-slate-300 text-xs font-semibold block">Önerilen Teklif Aralığı</span>
|
||
<span className="text-base font-black text-amber-300">
|
||
{tender.aiBidRange?.recommended != null
|
||
? `₺${tender.aiBidRange.recommended.toLocaleString('tr-TR')}`
|
||
: '—'}
|
||
</span>
|
||
</div>
|
||
<div className="bg-white/10 p-4 rounded-xl border border-white/10 space-y-1">
|
||
<span className="text-slate-300 text-xs font-semibold block">
|
||
{tender.gecerliTeklifSayisi != null || tender.teklifSayisi != null ? 'Katılan Firma' : 'Rekabet Verisi'}
|
||
</span>
|
||
<span className="text-lg font-black text-blue-300">
|
||
{tender.gecerliTeklifSayisi != null
|
||
? `${tender.gecerliTeklifSayisi} geçerli teklif`
|
||
: tender.teklifSayisi != null
|
||
? `${tender.teklifSayisi} teklif`
|
||
: 'Sonuçlanınca gelir'}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex justify-end pt-2">
|
||
<button
|
||
onClick={() => setIsAiSpecAnalyzerOpen(true)}
|
||
className="bg-amber-400 hover:bg-amber-300 text-slate-950 font-black px-4 py-2.5 rounded-xl text-xs flex items-center gap-2 transition-colors shadow-md cursor-pointer"
|
||
>
|
||
<Sparkles className="w-4 h-4 text-slate-900" />
|
||
<span>⚡ Şartname Yükle & AI Analizi Başlat</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<TenderDocuments tender={tender} />
|
||
|
||
<div className="bg-white p-5 rounded-xl border border-slate-200 space-y-3">
|
||
<h4 className="font-bold text-sm text-slate-900">Kritik Yeterlik Şartları</h4>
|
||
<ul className="space-y-2 text-xs text-slate-700">
|
||
{tender.yeterlikKriterleri?.map((k, i) => (
|
||
<li key={i} className="flex items-center gap-2 bg-slate-50 p-2.5 rounded-lg border border-slate-200 font-medium">
|
||
<span className="w-2 h-2 rounded-full bg-blue-600"></span>
|
||
<span>{k}</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Tab 5: Teklif Asistanı — belgeye sor / taslak denetimi / taslak üretimi */}
|
||
{activeTab === 'teklif_asistani' && <TeklifAsistani tender={tender} />}
|
||
|
||
{/* Tab 4: Readiness Checklist */}
|
||
{activeTab === 'checklist' && (
|
||
<div className="bg-white p-5 rounded-xl border border-slate-200 space-y-4">
|
||
<h3 className="font-bold text-sm text-slate-900">İhale Hazırlık Adımları</h3>
|
||
<div className="space-y-2 text-xs">
|
||
{[
|
||
'Birim fiyat cetveli kontrol edildi ve dolduruldu',
|
||
'İmza sirküleri ve yetki belgesi hazırlandı',
|
||
'ÜTS kayıtları ve SUT kodları doğrulandı',
|
||
'TCMB döviz kur riski hesaplandı',
|
||
'EKAP üzerinden teklif zarfı oluşturuldu'
|
||
].map((item, idx) => (
|
||
<label key={idx} 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-slate-800">
|
||
<input type="checkbox" className="w-4 h-4 accent-blue-600 rounded" />
|
||
<span>{item}</span>
|
||
</label>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|