import { deadlineInfo } from '../services/tenderCalendar'; import React, { useState, useEffect, useRef, useId } from 'react'; import {useModalFocus} from '../hooks/useModalFocus'; import { useApp } from '../context/AppContext'; import { X, Search, Loader2, Package, AlertTriangle, Building2, MapPin, CalendarDays, Info, } from 'lucide-react'; import { urunAra } from '../services/urunArama'; import { okasAra } from '../services/kalem'; /** * Ürün Arama — ürün adını ihale dokümanlarının içinde aratır. * * İhalelerin sadece başlığına bakmak yetersiz: "ORTOPEDİK VİDA" hiçbir * başlıkta geçmiyor ama üç ihalenin teklif cetvelinde var, ve o ihalelerin * adı "VETERİNER İLAÇ ... SARF MALZEME". Bu ekran o boşluğu kapatıyor. */ const SAYFA = 20; const gecmisMi = tarihSaat => { const [tarih, saat] = String(tarihSaat || '').trim().split(/\s+/); return deadlineInfo({tarih,saat}).expired === true; }; export const UrunAramaModal = () => { const { isUrunAramaOpen, setIsUrunAramaOpen, setActiveTenderDetail, openSearchTender, tenders, urunAramaBaslangic, setUrunAramaBaslangic, } = useApp(); const modalFocus=useModalFocus(isUrunAramaOpen,()=>setIsUrunAramaOpen(false)); const titleId = useId(); useEffect(() => { if (!isUrunAramaOpen) return; const previous = document.body.style.overflow; document.body.style.overflow = 'hidden'; return () => { document.body.style.overflow = previous; }; }, [isUrunAramaOpen]); const requestVersion = useRef(0); const [submitted, setSubmitted] = useState(null); useEffect(() => { if (!isUrunAramaOpen) { requestVersion.current++; setDurum((d) => ({ ...d, yukleniyor: false })); } }, [isUrunAramaOpen]); const [q, setQ] = useState(''); const [teknik, setTeknik] = useState(false); const [sadeceAcik, setSadeceAcik] = useState(true); const [durum, setDurum] = useState({ yukleniyor: false, sonuc: null, hata: null, sayfa: 0 }); // OKAS modu: idarenin kendi sınıflandırma koduyla arama. Serbest metinle // bulunamayan ("ortopedi" başlıkta hiç geçmiyor) ihaleleri kod yakalar. const [mod, setMod] = useState('metin'); // 'metin' | 'okas' const [okasQ, setOkasQ] = useState(''); const [okasAdaylar, setOkasAdaylar] = useState([]); const [okasSecili, setOkasSecili] = useState([]); // [{kod, ad}] // Sol menüden bir OKAS koduyla açıldıysa o kodla başla ve hemen ara useEffect(() => { if (!isUrunAramaOpen || !urunAramaBaslangic?.okasKod) return; setMod('okas'); setOkasSecili([{ kod: urunAramaBaslangic.okasKod, ad: urunAramaBaslangic.okasAd || '' }]); setUrunAramaBaslangic(null); araOkas([urunAramaBaslangic.okasKod], 0); // eslint-disable-next-line react-hooks/exhaustive-deps }, [isUrunAramaOpen, urunAramaBaslangic]); // OKAS adı yazdıkça katalogda ara (350 ms) useEffect(() => { const t = okasQ.trim(); if (mod !== 'okas' || t.length < 2) { setOkasAdaylar([]); return; } let iptal = false; const z = setTimeout(() => okasAra(t).then((k) => { if (!iptal) setOkasAdaylar(k); }).catch(() => {}), 350); return () => { iptal = true; clearTimeout(z); }; }, [okasQ, mod]); if (!isUrunAramaOpen) return null; const sorgula = async (query, sayfa = 0) => { const request = ++requestVersion.current; setDurum({ yukleniyor: true, sonuc: null, hata: null, sayfa }); try { const d = await urunAra(query.mod === 'metin' ? query.label : '', { skip: sayfa * SAYFA, take: SAYFA, teknik: query.teknik, okas: query.kodlar || [], }); if (request !== requestVersion.current) return; setSubmitted(query); setDurum({ yukleniyor: false, sonuc: d, hata: null, sayfa }); } catch (e) { if (request !== requestVersion.current) return; setDurum({ yukleniyor: false, sonuc: null, hata: e.message, sayfa }); } }; const araOkas = (kodlar, sayfa = 0) => { if (!kodlar.length) { setDurum((d) => ({ ...d, hata: 'En az bir OKAS kodu seçin.' })); return; } return sorgula({ mod: 'okas', label: kodlar.join(', '), kodlar: [...kodlar], teknik: false }, sayfa); }; const ara = () => { if (mod === 'okas') return araOkas(okasSecili.map((k) => k.kod)); const terim = q.trim(); if (terim.length < 3) { setDurum((d) => ({ ...d, hata: 'En az 3 karakter yazın.' })); return; } return sorgula({ mod: 'metin', label: terim, teknik }); }; const sorguDegisti = submitted && (submitted.mod !== mod || (mod === 'metin' ? submitted.label !== q.trim() || submitted.teknik !== teknik : submitted.label !== okasSecili.map((k) => k.kod).join(', '))); const okasEkle = (k) => { if (!okasSecili.some((x) => x.kod === k.kod)) setOkasSecili((o) => [...o, { kod: k.kod, ad: k.ad }]); setOkasQ(''); setOkasAdaylar([]); }; const sonuc = durum.sonuc; const liste = (sonuc?.list || []).filter((t) => !sadeceAcik || !gecmisMi(t.ihaleTarihSaat)); const gizlenen = (sonuc?.list?.length || 0) - liste.length; // Havuzdaki açık ihaleyle eşleşirse kartı doğrudan açabilelim const havuzda = (ikn) => tenders?.find((t) => t.ikn === ikn) || null; return (

Ürün Arama

Ürün adınızı ihale dokümanlarının içinde arar — başlıkta geçmese bile bulur.

{ e.preventDefault(); ara(0); }} className="space-y-3">
{[['metin', 'Ürün adıyla'], ['okas', 'OKAS koduyla']].map(([m, ad]) => ( ))}
{mod === 'okas' && (
setOkasQ(e.target.value)} placeholder="OKAS adı yazın — örn. ortopedik, röntgen, eldiven…" className="w-full text-sm pl-9 pr-3 py-2.5 rounded-xl border border-slate-300 focus:ring-2 focus:ring-indigo-500 outline-none" /> {okasAdaylar.length > 0 && (
{okasAdaylar.map((k) => ( ))}
)}
{okasSecili.length > 0 && (
{okasSecili.map((k) => ( {k.kod} {k.ad} ))}
)}
)}
{mod === 'metin' && (
setQ(e.target.value)} placeholder="Örn. ortopedik vida, hemogram kiti, kurşun önlük…" className="w-full text-sm pl-9 pr-3 py-2.5 rounded-xl border border-slate-300 focus:ring-2 focus:ring-indigo-500 outline-none" />
)} {mod === 'okas' &&
}
{mod === 'metin' && ( )}

Arama EKAP'ın doküman dizininde yapılır; hangi kalemin eşleştiğini EKAP döndürmüyor, yalnızca eşleşen ihaleleri veriyor. Kalem dökümünü görmek için ihalenin dokümanını yükleyin — kalemler çıkarılıp herkese açılır.

{durum.hata && (

{durum.hata}

)} {sonuc && sorguDegisti && (

Arama alanları değişti. Aşağıdaki sonuçlar ve sayfalar önceki aramaya aittir. Yeni seçimler için Ara düğmesine basın.

)} {sonuc && ( <>

{submitted?.mod === 'okas' ? <>OKAS {submitted?.label} kodlu{' '} : <>Teklif cetvelinde "{submitted?.label}" geçen{' '}} {sonuc.totalCount.toLocaleString('tr-TR')} ihale bulundu

{gizlenen > 0 && (

{gizlenen} tanesi tarihi geçtiği için gizlendi

)}
{liste.map((t) => { const acik = havuzda(t.ikn); return (

{t.baslik}

{t.tur} {t.ikn}
{t.kurum} {t.sehir && {t.sehir}} {t.ihaleTarihSaat && ( {t.ihaleTarihSaat} )} {t.durum} {/^\d{4}\/\d+$/.test(String(t.ikn || '')) && ( )}
); })} {liste.length === 0 && (

{sonuc.totalCount > 0 ? 'Bu sayfadaki sonuçların tarihi geçmiş. "Süresi geçtiği bilinenleri gizle" kutusunu kapatarak görebilirsiniz.' : 'Bu ürün adı hiçbir teklif cetvelinde bulunamadı. Daha kısa/genel bir terim deneyin.'}

)}
{sonuc.totalCount > SAYFA && (
{durum.sayfa + 1} / {Math.ceil(sonuc.totalCount / SAYFA).toLocaleString('tr-TR')}
)} )}
); };