535 lines
30 KiB
React
535 lines
30 KiB
React
import { TenderSourceHistory } from './TenderSourceHistory';
|
||
import React, { useState, useEffect, useId } from 'react';
|
||
import {useModalFocus} from '../hooks/useModalFocus';
|
||
import { useApp } from '../context/AppContext';
|
||
import { BidPackageBuilder } from './BidPackageBuilder';
|
||
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 { acknowledgeNoteDraft, editNoteDraft } from '../services/noteDrafts';
|
||
import { tenderStateKey } from '../services/tenderState';
|
||
import { createTenderICS } from '../services/tenderCalendar';
|
||
|
||
export const TenderDetailModal = () => {
|
||
const { activeTenderDetail, setActiveTenderDetail, toggleTrackTender, userProfile, setIsAiSpecAnalyzerOpen, gorevAc, gorevKontrol, aiAnalysisResults, tenders, mutateTender, savingCount, saveError, authUser, detailLoad, retryTenderDetail } = useApp();
|
||
const dialogTitleId = useId();
|
||
const isOpen = Boolean(activeTenderDetail);
|
||
const {ref:dialogRef,onKeyDown:handleDialogKey}=useModalFocus(isOpen,()=>setActiveTenderDetail(null));
|
||
const [activeTab, setActiveTab] = useState('details'); // 'details', 'pdf_stamp', 'ai_assistant', 'checklist', 'kurum_tenders'
|
||
useEffect(()=>{setActiveTab(activeTenderDetail?.preparationTarget ? 'pdf_stamp' : 'details');},[activeTenderDetail?.id,activeTenderDetail?.preparationTarget]);
|
||
const noteOwner = authUser?.email || '';
|
||
const [noteDrafts, setNoteDrafts] = useState(() => ({ owner: noteOwner, drafts: {} }));
|
||
// Discard private drafts on account changes, before rendering another account.
|
||
if (noteDrafts.owner !== noteOwner) setNoteDrafts({ owner: noteOwner, drafts: {} });
|
||
const noteKey = tenderStateKey(activeTenderDetail);
|
||
const noteDraft = noteDrafts.owner === noteOwner ? noteDrafts.drafts[noteKey] : undefined;
|
||
const noteText = noteDraft?.text || '';
|
||
const setNoteText = text => setNoteDrafts(state => editNoteDraft(state, noteOwner, noteKey, text));
|
||
const [actionError, setActionError] = useState(null);
|
||
|
||
// Kur Farkı Calculator State
|
||
const [bidAmount, setBidAmount] = useState(15000);
|
||
const [fx, setFx] = useState(null);
|
||
const [fxError, setFxError] = useState(null);
|
||
useEffect(() => {
|
||
if (!activeTenderDetail) return;
|
||
let cancelled = false;
|
||
fetch('/api/exchange-rates').then(r => r.json()).then(d => {
|
||
if (cancelled) return;
|
||
if (!d.success) throw new Error(d.error);
|
||
setFx(d); setFxError(d.refreshError || null);
|
||
}).catch(e => { if (!cancelled) setFxError(e.message); });
|
||
return () => { cancelled = true; };
|
||
}, [activeTenderDetail?.id]);
|
||
const usdRate = fx?.rates?.USD;
|
||
const eurRate = fx?.rates?.EUR;
|
||
|
||
if (!activeTenderDetail) return null;
|
||
// Havuzdaki güncel kayıt: takip/görev değişince düğme etiketleri de değişsin.
|
||
const tender = (activeTenderDetail && tenders.find((t) => t.id === activeTenderDetail.id)) || activeTenderDetail;
|
||
|
||
const savedNotes = tender.notes || [];
|
||
const handleAddNote = async (e) => {
|
||
e.preventDefault();
|
||
if (!noteText.trim()) return;
|
||
const saved = await mutateTender(tender.id, {notes: [...savedNotes, {
|
||
id: Date.now(),
|
||
author: userProfile.name,
|
||
text: noteText.trim(),
|
||
date: new Date().toLocaleString('tr-TR')
|
||
}]});
|
||
if (saved) setNoteDrafts(state => acknowledgeNoteDraft(state, noteOwner, noteKey, noteDraft));
|
||
};
|
||
|
||
const handleExportICS = () => {
|
||
let icsData;
|
||
try { icsData = createTenderICS(tender); setActionError(null); }
|
||
catch (e) { setActionError(e.message); return; }
|
||
|
||
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);
|
||
setTimeout(() => URL.revokeObjectURL(link.href), 1000);
|
||
};
|
||
|
||
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 ref={dialogRef} role="dialog" aria-modal="true" aria-labelledby={dialogTitleId} tabIndex={-1} onKeyDown={handleDialogKey} 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]">
|
||
|
||
{(actionError || saveError) && <p role="alert" className="p-3 bg-red-50 text-red-900">{actionError || saveError}</p>}
|
||
{tender.ikn && <TenderSourceHistory key={`${authUser?.email}:${tender.ikn}:${tender.kurum}`} tender={tender} />}
|
||
{tender.sourceChanges?.length > 0 && <div role="alert" className="p-3 bg-amber-50 text-amber-900 text-sm">
|
||
<p className="font-bold">Son kaydettiğiniz bilgilere göre ihale değişmiş</p>
|
||
<ul className="list-disc pl-5">{tender.sourceChanges.map(change => <li key={change.field}>{change.label}: {change.before} → {change.after}</li>)}</ul>
|
||
<p>Takip, not ve hazırlık kayıtlarınız korundu. Yeni koşulları resmi kaynakla karşılaştırın.</p>
|
||
</div>}
|
||
{tender.sourceWarning && <p role="alert" className="p-3 bg-amber-50 text-amber-900 text-sm">{tender.sourceWarning}</p>}
|
||
{detailLoad?.id === tender.id && detailLoad.status === 'loading' && <p role="status" className="p-3 bg-blue-50 text-blue-900">Kaynak ilan metni yükleniyor…</p>}
|
||
{detailLoad?.id === tender.id && detailLoad.status === 'error' && <div role="alert" className="p-3 bg-amber-50 text-amber-900 text-sm">
|
||
<p>{detailLoad.error}</p>
|
||
<button type="button" onClick={retryTenderDetail} className="mt-2 underline font-bold">İlan metnini yeniden yükle</button>
|
||
</div>}
|
||
{savingCount > 0 && <p role="status" className="p-2 bg-blue-50">Kaydediliyor…</p>}
|
||
{/* 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">
|
||
<button type="button" disabled={savingCount > 0} aria-pressed={Boolean(tender.incelendi)}
|
||
onClick={() => mutateTender(tender.id,{incelendi:!tender.incelendi})}
|
||
className="text-xs px-3 py-1.5 rounded-lg border border-slate-500 disabled:opacity-50">
|
||
{tender.incelendi ? 'İncelendi — yeniden aç' : 'İncelendi olarak işaretle'}
|
||
</button>
|
||
<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)}
|
||
aria-label="İhale detayını kapat"
|
||
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 id={dialogTitleId} 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: 'Teklif Dosyası Hazırla', icon: Stamp, badge: 'İnteraktif' },
|
||
{ id: 'ai_assistant', label: 'Yapay Zeka Asistanı', icon: Sparkles, badge: tender?.aiMatchScore != null ? `%${tender.aiMatchScore} Uyum` : undefined },
|
||
{ 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} knownItemCount={tender.items?.length || 0} />
|
||
|
||
{/* 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)}
|
||
aria-label="Ekip notu"
|
||
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" disabled={savingCount > 0 || !noteText.trim()} className="disabled:opacity-50 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 döviz satış</span>
|
||
</div>
|
||
|
||
{fx && <p className="text-[11px]"><a href={fx.source} target="_blank" rel="noreferrer" className="underline">TCMB bülteni: {fx.bulletinDate}</a> · Alındı: {new Date(fx.fetchedAt).toLocaleString('tr-TR')}</p>}
|
||
{fxError && <p role="alert" className="text-xs text-amber-200">{fxError}</p>}
|
||
<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 ? `₺${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 ? `₺${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">{usdRate ? '$' + (bidAmount / usdRate).toFixed(2) : 'Kur bekleniyor'}</span>
|
||
</div>
|
||
<div className="flex justify-between text-slate-300">
|
||
<span>Euro Karşılığı:</span>
|
||
<span className="font-bold text-white">{eurRate ? '€' + (bidAmount / eurRate).toFixed(2) : 'Kur bekleniyor'}</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" /> Teklif Dosyası Hazırla (PDF / Excel)
|
||
</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" /> Takvim Dosyasını İndir (.ics)
|
||
</button>
|
||
<p className="text-xs text-slate-600 mt-2">Bu dosya otomatik güncellenmez. Tarih veya saat değişirse Google/Outlook takviminizdeki eski kaydı güncelleyin; yeni dosyayı içe aktarmak ikinci bir kayıt oluşturabilir.</p>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
)}
|
||
|
||
{/* Tab 2: Teklif dosyası hazırlığı */}
|
||
{activeTab === 'pdf_stamp' && (
|
||
<BidPackageBuilder key={tender.ikn || tender.id} tender={tender} initialFocus={activeTenderDetail?.preparationTarget}
|
||
results={aiAnalysisResults?.tenderKey === (tender.ikn || tender.id) ? aiAnalysisResults : null} />
|
||
)}
|
||
|
||
{/* 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">Analizi başlatarak işlenen kaynağı ve kontrol gerektiren alanları inceleyin.</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>
|
||
);
|
||
};
|