Expose partial item search failures and allow query-scoped retries
This commit is contained in:
@@ -16,7 +16,7 @@ export const TenderList = () => {
|
||||
const {
|
||||
tenders,
|
||||
searchQuery,
|
||||
kalemEslesen,
|
||||
kalemEslesen, itemSearchState, retryItemSearch,
|
||||
uyumEsigi,
|
||||
selectedCity,
|
||||
selectedAlimTuru,
|
||||
@@ -174,6 +174,11 @@ export const TenderList = () => {
|
||||
</div>
|
||||
|
||||
{sortBy === 'newest' && <p className="text-xs text-slate-500">Yayın tarihi bilinmeyen kayıtlar sonda gösterilir. Aynı gün içinde saati bilinen kayıtlar önce gelir.</p>}
|
||||
{itemSearchState?.status === 'loading' && <p role="status" className="text-xs text-slate-600">Doküman kalemlerinde arama sürüyor; listedeki sonuçlar henüz genişleyebilir.</p>}
|
||||
{itemSearchState?.status === 'error' && <div role="alert" className="rounded-lg border border-amber-200 bg-amber-50 p-3 text-xs text-amber-900">
|
||||
<p>{itemSearchState.error} Başlık, kurum ve yüklü kart bilgileriyle arama sürüyor; sonuçlar eksik olabilir.</p>
|
||||
<button type="button" onClick={retryItemSearch} className="mt-1 underline font-semibold">Kalem aramasını yeniden dene</button>
|
||||
</div>}
|
||||
{preparedExport && preparedExport.session === getToken() && <div role="status" className="rounded-lg border border-emerald-200 bg-emerald-50 p-3 text-xs space-y-1">
|
||||
<p>{preparedExport.createdAt} itibarıyla {preparedExport.count} ihale için Excel hazır{preparedExport.query ? ` — arama: ${preparedExport.query}` : ''}.</p>
|
||||
<a href={preparedExport.url} download={preparedExport.name} className="font-bold text-emerald-800 underline">Excel dosyasını indir — {preparedExport.name}</a>
|
||||
|
||||
@@ -58,21 +58,26 @@ export const AppProvider = ({ children }) => {
|
||||
// ilk birkaç kalem taşınıyor, tam arama sunucuda yapılıyor.
|
||||
const [kalemSonucu, setKalemSonucu] = useState(null);
|
||||
const kalemEslesen = matchingItemResults(kalemSonucu, searchQuery);
|
||||
const [kalemRetry,setKalemRetry] = useState(0);
|
||||
const retryItemSearch = () => setKalemRetry(n=>n+1);
|
||||
const itemSearchState = kalemSonucu?.query === normalizeTenderSearch(searchQuery) ? kalemSonucu : null;
|
||||
useEffect(() => {
|
||||
const q = searchQuery.trim();
|
||||
if (q.length < 2) { setKalemSonucu(null); return; }
|
||||
let iptal = false;
|
||||
const controller=new AbortController();
|
||||
setKalemSonucu({query:normalizeTenderSearch(q),status:'loading',matches:null});
|
||||
const zaman = setTimeout(() => {
|
||||
import('../services/kalem')
|
||||
.then(({ kalemAra }) => kalemAra(q))
|
||||
.then(({ kalemAra }) => kalemAra(q,{signal:controller.signal}))
|
||||
.then((sonuclar) => {
|
||||
if (iptal) return;
|
||||
setKalemSonucu({query:normalizeTenderSearch(q),matches:sonuclar.length ? new Map(sonuclar.map((s) => [s.ikn, s.eslesen])) : null});
|
||||
setKalemSonucu({query:normalizeTenderSearch(q),status:'loaded',matches:sonuclar.length ? new Map(sonuclar.map((s) => [s.ikn, s.eslesen])) : null});
|
||||
})
|
||||
.catch(() => { if (!iptal) setKalemSonucu(null); });
|
||||
.catch(error => { if (!iptal) setKalemSonucu({query:normalizeTenderSearch(q),status:'error',error:error.message,matches:null}); });
|
||||
}, 350);
|
||||
return () => { iptal = true; clearTimeout(zaman); };
|
||||
}, [searchQuery]);
|
||||
return () => { iptal = true; controller.abort(); clearTimeout(zaman); };
|
||||
}, [searchQuery,kalemRetry]);
|
||||
const [selectedCity, setSelectedCity] = useState('');
|
||||
const [selectedAlimTuru, setSelectedAlimTuru] = useState('');
|
||||
const [selectedKurumTipi, setSelectedKurumTipi] = useState('');
|
||||
@@ -725,7 +730,7 @@ export const AppProvider = ({ children }) => {
|
||||
handleLogout,
|
||||
liveStats,
|
||||
searchQuery,
|
||||
kalemEslesen,
|
||||
kalemEslesen, itemSearchState, retryItemSearch,
|
||||
setSearchQuery,
|
||||
selectedCity,
|
||||
setSelectedCity,
|
||||
|
||||
+15
-5
@@ -26,11 +26,21 @@ export async function kalemleriGetir(ikn, {signal, timeoutMs = 30000} = {}) {
|
||||
}
|
||||
|
||||
/** Kalem metninde arama — eşleşen İKN'ler. */
|
||||
export async function kalemAra(q) {
|
||||
const r = await fetch(`/api/kalem/ara?q=${encodeURIComponent(q)}`);
|
||||
const d = await r.json().catch(() => ({}));
|
||||
if (!d.success) return [];
|
||||
return d.sonuclar || [];
|
||||
export async function kalemAra(q,{signal,timeoutMs=30000}={}) {
|
||||
const controller=new AbortController();
|
||||
const cancel=()=>controller.abort();
|
||||
if(signal?.aborted)cancel();else signal?.addEventListener('abort',cancel,{once:true});
|
||||
let timedOut=false;
|
||||
const timer=setTimeout(()=>{timedOut=true;controller.abort();},timeoutMs);
|
||||
try {
|
||||
const r=await fetch(`/api/kalem/ara?q=${encodeURIComponent(q)}`,{signal:controller.signal});
|
||||
const d=await r.json().catch(()=>null);
|
||||
if(!r.ok || d?.success!==true || !Array.isArray(d.sonuclar))throw new Error('Doküman kalemlerinde arama tamamlanamadı.');
|
||||
return d.sonuclar;
|
||||
} catch(error) {
|
||||
if(timedOut)throw new Error('Doküman kalemlerinde arama zaman aşımına uğradı.');
|
||||
throw error;
|
||||
} finally {clearTimeout(timer);signal?.removeEventListener('abort',cancel);}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user