AB İhaleleri (TED): başlık ve açıklamalar Türkçe
- server/ted-ceviri.mjs: TED'in kendi Türkçe alanı varsa onu kullanır; yoksa OpenRouter ile toplu çevirir (deepseek-v4-flash → gemini 3.7 flash → glm), sonucu server/data/ted.sqlite'ta ilan numarasına göre saklar - İstek en fazla 12 sn bekler, kalan çeviriler arka planda sürer; yanıt ceviriBekleniyor bayrağı taşır, ön yüz sessizce yeniden çeker - Kartta Türkçe başlık + küçük özgün başlık, Türkçe açıklama (3 satır), ülke kodları Türkçe ad (TUR → Türkiye, AUT → Avusturya…) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -7,20 +7,38 @@ const MODLAR = [
|
||||
{ id: 'kazanan', label: 'AB’de Kazanan TR Firmaları', aciklama: 'Türk firmalarının AB ihalelerinde kazandığı sözleşmeler.' },
|
||||
];
|
||||
|
||||
const ULKE_TR = {
|
||||
TUR: 'Türkiye', DEU: 'Almanya', FRA: 'Fransa', ITA: 'İtalya', ESP: 'İspanya', NLD: 'Hollanda', BEL: 'Belçika',
|
||||
AUT: 'Avusturya', POL: 'Polonya', ROU: 'Romanya', BGR: 'Bulgaristan', GRC: 'Yunanistan', HRV: 'Hırvatistan',
|
||||
HUN: 'Macaristan', CZE: 'Çekya', SVK: 'Slovakya', SVN: 'Slovenya', PRT: 'Portekiz', SWE: 'İsveç', DNK: 'Danimarka',
|
||||
FIN: 'Finlandiya', NOR: 'Norveç', IRL: 'İrlanda', LTU: 'Litvanya', LVA: 'Letonya', EST: 'Estonya', CYP: 'Kıbrıs',
|
||||
MLT: 'Malta', LUX: 'Lüksemburg', CHE: 'İsviçre', GBR: 'İngiltere', SRB: 'Sırbistan', MKD: 'K. Makedonya',
|
||||
ALB: 'Arnavutluk', BIH: 'Bosna-Hersek', MNE: 'Karadağ', XKX: 'Kosova', UKR: 'Ukrayna', MDA: 'Moldova', ISL: 'İzlanda',
|
||||
};
|
||||
|
||||
export const TedTenders = () => {
|
||||
const [mod, setMod] = useState('acik');
|
||||
const [data, setData] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const yukle = (m) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
// Çeviriler sunucuda arka planda tamamlanır; "bekleniyor" geldiği sürece
|
||||
// sessizce (yükleniyor göstergesi olmadan) birkaç kez yeniden çekilir.
|
||||
const yenidenDeneme = React.useRef(0);
|
||||
const yukle = (m, sessiz = false) => {
|
||||
if (!sessiz) { setLoading(true); setError(null); yenidenDeneme.current = 0; }
|
||||
fetch(`/api/ted?mod=${m}&limit=30`)
|
||||
.then((r) => r.json())
|
||||
.then((d) => { if (d.success) setData(d); else setError(d.error); })
|
||||
.catch((e) => setError(e.message))
|
||||
.finally(() => setLoading(false));
|
||||
.then((d) => {
|
||||
if (!d.success) { if (!sessiz) setError(d.error); return; }
|
||||
setData(d);
|
||||
if (d.ceviriBekleniyor && yenidenDeneme.current < 12) {
|
||||
yenidenDeneme.current += 1;
|
||||
setTimeout(() => yukle(m, true), 7000);
|
||||
}
|
||||
})
|
||||
.catch((e) => { if (!sessiz) setError(e.message); })
|
||||
.finally(() => { if (!sessiz) setLoading(false); });
|
||||
};
|
||||
|
||||
useEffect(() => { yukle(mod); }, [mod]);
|
||||
@@ -92,6 +110,9 @@ export const TedTenders = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data?.ceviriBekleniyor && (
|
||||
<p className="text-[11px] text-slate-500 flex items-center gap-1"><Loader2 className="w-3 h-3 animate-spin" /> Başlıklar Türkçeye çevriliyor, birkaç saniye içinde güncellenir…</p>
|
||||
)}
|
||||
{data && (
|
||||
<div className="space-y-3">
|
||||
{data.tenders.length === 0 && (
|
||||
@@ -102,7 +123,12 @@ export const TedTenders = () => {
|
||||
{data.tenders.map((t) => (
|
||||
<div key={t.id} className="bg-white rounded-xl border border-slate-200 p-4 shadow-xs hover:shadow-md transition-shadow space-y-2">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<h3 className="font-bold text-sm text-slate-900 leading-snug">{t.baslik}</h3>
|
||||
<div className="min-w-0">
|
||||
<h3 className="font-bold text-sm text-slate-900 leading-snug">{t.baslikTr || t.baslik}</h3>
|
||||
{t.baslikTr && t.baslikTr !== t.baslik && (
|
||||
<p className="text-[11px] italic text-slate-400 leading-snug mt-0.5">{t.baslik}</p>
|
||||
)}
|
||||
</div>
|
||||
{t.link && (
|
||||
<a href={t.link} target="_blank" rel="noopener noreferrer"
|
||||
className="text-sky-600 hover:text-sky-800 shrink-0" title="TED'de aç">
|
||||
@@ -116,7 +142,7 @@ export const TedTenders = () => {
|
||||
<Building2 className="w-3 h-3 text-slate-400" /> {t.kurum}
|
||||
</span>
|
||||
{t.sehir && <span>{t.sehir}</span>}
|
||||
{t.ulke && <span className="bg-slate-100 px-1.5 py-0.5 rounded font-bold">{t.ulke}</span>}
|
||||
{t.ulke && <span className="bg-slate-100 px-1.5 py-0.5 rounded font-bold">{ULKE_TR[t.ulke] || t.ulke}</span>}
|
||||
<span className="flex items-center gap-1">
|
||||
<Calendar className="w-3 h-3 text-blue-500" />
|
||||
{t.sonTeklifTarihi ? `Son teklif: ${t.sonTeklifTarihi}` : t.yayimTarihi}
|
||||
@@ -125,12 +151,16 @@ export const TedTenders = () => {
|
||||
{t.sonucIlani && <span className="bg-emerald-50 text-emerald-800 border border-emerald-200 px-1.5 py-0.5 rounded font-bold">Sonuç İlanı</span>}
|
||||
</div>
|
||||
|
||||
{(t.aciklamaTr || t.aciklama) && (
|
||||
<p className="text-xs text-slate-600 leading-relaxed line-clamp-3">{t.aciklamaTr || t.aciklama}</p>
|
||||
)}
|
||||
|
||||
{(t.kazananFirma || t.yaklasikMaliyet != null) && (
|
||||
<div className="flex items-center gap-4 flex-wrap pt-2 border-t border-slate-100 text-[11px]">
|
||||
{t.kazananFirma && (
|
||||
<span className="flex items-center gap-1 font-bold text-emerald-800">
|
||||
<Trophy className="w-3 h-3" /> {t.kazananFirma}
|
||||
{t.kazananUlke && <span className="text-slate-400 font-normal">({t.kazananUlke})</span>}
|
||||
{t.kazananUlke && <span className="text-slate-400 font-normal">({ULKE_TR[t.kazananUlke] || t.kazananUlke})</span>}
|
||||
</span>
|
||||
)}
|
||||
{t.yaklasikMaliyet != null && (
|
||||
|
||||
Reference in New Issue
Block a user