feat: ortak kalem havuzu — teklif cetveli kalemleri kartta gorunur ve aranabilir
Firmalar ihalenin sadece basligina bakarak kendi urunlerinin icinde olup
olmadigini goremiyordu ("148 KALEM KIRTASIYE MALZEME ALIMI"). Artik bir
kullanici ihale dokumanini yukledinde teklif cetveli kalemleri ayristirilip
IKN'ye baglaniyor ve o ihale HERKESIN kartinda kalemleriyle gorunuyor.
NEDEN YUKLEMEYE DAYALI: kalem listesi EKAP'in programatik yuzeyinde yok.
- Arama/detay API'si (ihtiyacKalemiOkasList) yalnizca OKAS sinifi veriyor:
"KALP AKCIGER POMPASI CIHAZI ALIMI" icin tek satir "Tibbi cihazlar"
- Ilan metni yalnizca sayiyi veriyor: "148 KALEM ... Ayrintili bilgiye EKAP'ta
yer alan ihale dokumani icinde bulunan idari sartnameden ulasilabilir"
- Dokuman sayfasi CAPTCHA ile korunuyor (ctl00_btnCaptchaProtect, guvenlik
resmi). Gercek tarayiciyla (Playwright) da acildi, donen tek metin: "Dogrula"
OTOMATIK, AI'SIZ: kalem cikarimi yukleme aninda kendiliginden calisir,
kullanicidan tiklama beklemez ve model cagirmaz (saf regex). AI analizi ayri
dugmede, kullanici isterse.
- server/kalem-cikar.mjs: teklif cetveli ayristirici. " | " tablolarindan
(docx/xlsx/zip) sutun basligini cozerek; ayracsiz PDF metninde sezgisel
satir kalibiyla. Miktar+birim tek hucrede olabiliyor ("25000 TEST").
Birim listesinde 'test' var — tibbi kitlerde kalem miktari boyle yaziliyor.
- server/kalem-store.mjs: kalem.sqlite, IKN bazli, Turkce duyarli arama.
Bos ayristirma mevcut iyi veriyi silmez.
- Uclar: POST /api/kalem/cikar (tarayicidan gelen PDF metni), GET /api/kalem,
GET /api/kalem/ara, GET /api/health/kalem
- /api/tenders payloadu kalemSayisi + ilk kalemleri tasiyor
- Kartta kalem rozeti ve kalem seridi; aramada eslesen kalemler one alinir
- Ust arama kutusu kalem havuzunda da ariyor (350ms debounce, sunucu tarafi)
- Detay modalinda tam kalem listesi + kalem ici arama
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,26 @@ export const AppProvider = ({ children }) => {
|
||||
|
||||
const [liveStats, setLiveStats] = useState({ toplamIhale: 0, bugunEklenen: 0, arsiv: 0 });
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
|
||||
// Kalem havuzunda arama: firmanın kendi ürününü ("eldiven", "ortopedik vida")
|
||||
// hangi ihalelerin teklif cetvelinde geçtiğini bulması için. Kartta yalnızca
|
||||
// ilk birkaç kalem taşınıyor, tam arama sunucuda yapılıyor.
|
||||
const [kalemEslesen, setKalemEslesen] = useState(null); // Map(ikn → eşleşen kalemler)
|
||||
useEffect(() => {
|
||||
const q = searchQuery.trim();
|
||||
if (q.length < 2) { setKalemEslesen(null); return; }
|
||||
let iptal = false;
|
||||
const zaman = setTimeout(() => {
|
||||
import('../services/kalem')
|
||||
.then(({ kalemAra }) => kalemAra(q))
|
||||
.then((sonuclar) => {
|
||||
if (iptal) return;
|
||||
setKalemEslesen(sonuclar.length ? new Map(sonuclar.map((s) => [s.ikn, s.eslesen])) : null);
|
||||
})
|
||||
.catch(() => { if (!iptal) setKalemEslesen(null); });
|
||||
}, 350);
|
||||
return () => { iptal = true; clearTimeout(zaman); };
|
||||
}, [searchQuery]);
|
||||
const [selectedCity, setSelectedCity] = useState('');
|
||||
const [selectedAlimTuru, setSelectedAlimTuru] = useState('');
|
||||
const [selectedKurumTipi, setSelectedKurumTipi] = useState('');
|
||||
@@ -531,6 +551,7 @@ export const AppProvider = ({ children }) => {
|
||||
handleLogout,
|
||||
liveStats,
|
||||
searchQuery,
|
||||
kalemEslesen,
|
||||
setSearchQuery,
|
||||
selectedCity,
|
||||
setSelectedCity,
|
||||
|
||||
Reference in New Issue
Block a user