118 lines
5.4 KiB
React
118 lines
5.4 KiB
React
import React, { useEffect, useState } from 'react';
|
||
import { Package, Loader2, Search } from 'lucide-react';
|
||
import { kalemleriGetir } from '../services/kalem';
|
||
|
||
/**
|
||
* Teklif cetveli kalemleri — ortak havuzdan.
|
||
*
|
||
* Kalemler EKAP API'sinde yok; doküman sayfası CAPTCHA ile korunduğu için
|
||
* sunucu otomatik indiremiyor. Bir kullanıcı ihale dokümanını yüklediğinde
|
||
* kalemler ayrıştırılıp havuza yazılıyor ve bu liste herkese görünür oluyor.
|
||
*/
|
||
export const TeklifCetveliKalemleri = ({ikn, knownItemCount = 0}) => <SharedItems key={ikn || 'unknown'} ikn={ikn} knownItemCount={knownItemCount} />;
|
||
|
||
const SharedItems = ({ikn, knownItemCount}) => {
|
||
const [durum, setDurum] = useState({ yukleniyor: true, kalemler: [], kaynak: null });
|
||
const [suzgec, setSuzgec] = useState('');
|
||
const [retry, setRetry] = useState(0);
|
||
|
||
useEffect(() => {
|
||
if (!ikn) { setDurum({ yukleniyor: false, kalemler: [], kaynak: null }); return; }
|
||
let iptal = false;
|
||
const controller = new AbortController();
|
||
setDurum({yukleniyor:true,kalemler:[],kaynak:null});
|
||
kalemleriGetir(ikn, {signal:controller.signal})
|
||
.then((d) => { if (!iptal) setDurum({ yukleniyor: false, kalemler: d.kalemler || [], kaynak: d.kaynak }); })
|
||
.catch(error => { if (!iptal) setDurum({ yukleniyor:false, kalemler:[], kaynak:null, hata:error.message }); });
|
||
return () => { iptal = true; controller.abort(); };
|
||
}, [ikn, retry]);
|
||
|
||
if (durum.yukleniyor) {
|
||
return (
|
||
<p className="text-xs text-slate-500 flex items-center gap-2">
|
||
<Loader2 className="w-3.5 h-3.5 animate-spin" /> Teklif cetveli kalemleri okunuyor…
|
||
</p>
|
||
);
|
||
}
|
||
|
||
if (durum.hata) return <div role="alert" className="rounded-xl border border-amber-200 bg-amber-50 p-3 text-xs text-amber-900">
|
||
<p>Ortak doküman kalemleri yüklenemedi. {durum.hata}</p>
|
||
<button type="button" onClick={() => setRetry(n => n + 1)} className="mt-2 underline font-semibold">Kalemleri yeniden yükle</button>
|
||
</div>;
|
||
|
||
if (!durum.kalemler.length) {
|
||
return (
|
||
<div className="bg-slate-50 border border-slate-200 rounded-xl p-4">
|
||
<h3 className="font-bold text-sm text-slate-900 flex items-center gap-2">
|
||
<Package className="w-4 h-4 text-indigo-600" /> Teklif Cetveli Kalemleri
|
||
</h3>
|
||
<p className="text-xs text-slate-600 leading-relaxed mt-1.5">
|
||
{knownItemCount > 0
|
||
? `İhale kaydında aşağıda gösterilen ${knownItemCount} kalem var. Ortak doküman havuzunda ayrıca çıkarılmış bir cetvel bulunamadı; mevcut kalemleri resmi dokümanla karşılaştırın.`
|
||
: 'Ortak doküman havuzunda bu ihale için kalem bulunamadı. Resmi ihale dokümanını yükleyerek kalemleri çıkarmayı deneyebilirsiniz.'}
|
||
{' '}İhale dokümanından ortak havuza aktarılan kalemler diğer kullanıcılara da görünür.
|
||
|
||
</p>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
const q = suzgec.trim().toLocaleLowerCase('tr-TR');
|
||
const gorunen = q
|
||
? durum.kalemler.filter((k) => (k.ad || '').toLocaleLowerCase('tr-TR').includes(q))
|
||
: durum.kalemler;
|
||
|
||
return (
|
||
<div className="space-y-2">
|
||
<div className="flex items-center justify-between gap-3 flex-wrap">
|
||
<h3 className="font-bold text-sm text-slate-900 flex items-center gap-2">
|
||
<Package className="w-4 h-4 text-indigo-600" />
|
||
Ortak Doküman Kalemleri ({durum.kalemler.length})
|
||
</h3>
|
||
<label className="relative">
|
||
<Search className="w-3.5 h-3.5 text-slate-400 absolute left-2.5 top-1/2 -translate-y-1/2" />
|
||
<input
|
||
value={suzgec}
|
||
onChange={(e) => setSuzgec(e.target.value)}
|
||
placeholder="Kalem ara…"
|
||
className="text-xs pl-8 pr-3 py-1.5 rounded-lg border border-slate-300 w-44 focus:ring-2 focus:ring-indigo-500 outline-none"
|
||
/>
|
||
</label>
|
||
</div>
|
||
|
||
<div className="border border-slate-200 rounded-xl overflow-hidden shadow-2xs max-h-96 overflow-y-auto">
|
||
<table className="w-full text-left text-xs">
|
||
<thead className="bg-slate-100 text-slate-700 font-bold border-b border-slate-200 sticky top-0">
|
||
<tr>
|
||
<th className="p-2.5 w-12">#</th>
|
||
<th className="p-2.5">Kalem adı</th>
|
||
<th className="p-2.5 text-right w-32">Miktar</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody className="divide-y divide-slate-100">
|
||
{gorunen.map((k, i) => (
|
||
<tr key={i} className="hover:bg-indigo-50/40">
|
||
<td className="p-2.5 font-bold text-slate-400">{k.sira ?? i + 1}</td>
|
||
<td className="p-2.5 font-semibold text-slate-800">{k.ad}</td>
|
||
<td className="p-2.5 text-right font-bold text-slate-900 whitespace-nowrap">
|
||
{k.miktar != null ? k.miktar.toLocaleString('tr-TR') : '—'} {k.birim || ''}
|
||
</td>
|
||
</tr>
|
||
))}
|
||
{gorunen.length === 0 && (
|
||
<tr><td colSpan={3} className="p-3 text-center text-slate-400">Bu aramayla eşleşen kalem yok.</td></tr>
|
||
)}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
{durum.kaynak?.dosya && (
|
||
<p className="text-[10px] text-slate-400">
|
||
Kaynak: <strong>{durum.kaynak.dosya}</strong> — bir kullanıcının yüklediği ihale dokümanından
|
||
otomatik çıkarıldı. Teklif vermeden önce şartnamenin aslıyla karşılaştırın.
|
||
</p>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|