feat: İhaleAB'den taşınan özellikler — MCP sunucusu, RSS, haftalık brief, teklif asistanı, yeterlilik rozeti, 5 yeni kırmızı bayrak
- /mcp (JSON-RPC, ih_live_ anahtarı): search_tenders, get_tender, search_results, search_items, upcoming_deadlines - /api/feeds/tenders.rss (anahtarlı) ve /api/feeds/sonuc.rss (açık) - Haftalık brief (Pazartesi 08:00, Telegram + e-posta), /api/me/brief tercihi + önizleme, sağ panelde yaklaşan ihale tarihleri - Detayda Teklif Asistanı sekmesi: belgeye sor (atıflı), taslak denetimi, taslak üretimi (analyzeSpec zinciri) - Derin analiz belge listesi × Doküman Deposu eşleştirme rozeti - Kırmızı bayrak: kısa süre, hafta sonu tarih, geç sözleşme, kazananı gizli, DT parçalama şüphesi - Kullanım kılavuzu güncellendi Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { CalendarClock } from 'lucide-react';
|
||||
import { useApp } from '../context/AppContext';
|
||||
import { parseDateTR } from '../services/analytics';
|
||||
|
||||
/**
|
||||
* Sağ panel: takipteki / panodaki ihalelerden 14 gün içinde yapılacaklar.
|
||||
* Sunucuya gitmez — liste zaten bellekte. ≤3 gün kırmızı, ≤7 turuncu.
|
||||
*/
|
||||
export const YaklasanSonTarihler = ({ gun = 14 }) => {
|
||||
const { tenders, setActiveTenderDetail } = useApp();
|
||||
const liste = useMemo(() => {
|
||||
const bugun = new Date(); bugun.setHours(0, 0, 0, 0);
|
||||
const sinir = new Date(bugun.getTime() + gun * 86400000);
|
||||
return tenders
|
||||
.filter((t) => t.takipte || t.kanbanStatus || t.gorev)
|
||||
.map((t) => ({ t, d: parseDateTR(t.tarih) }))
|
||||
.filter(({ d }) => d && d >= bugun && d <= sinir)
|
||||
.sort((a, b) => a.d - b.d)
|
||||
.map(({ t, d }) => ({ t, kalan: Math.round((d - bugun) / 86400000) }))
|
||||
.slice(0, 8);
|
||||
}, [tenders, gun]);
|
||||
|
||||
const renk = (g) => (g <= 3 ? 'bg-red-600 text-white' : g <= 7 ? 'bg-amber-500 text-white' : 'bg-slate-200 text-slate-800');
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-xl border border-slate-200 shadow-xs overflow-hidden">
|
||||
<div className="bg-slate-50 border-b border-slate-100 px-4 py-3">
|
||||
<span className="flex items-center gap-2 font-bold text-xs uppercase tracking-wider text-slate-800">
|
||||
<CalendarClock className="w-4 h-4 text-amber-500" /> Yaklaşan İhale Tarihleri
|
||||
</span>
|
||||
</div>
|
||||
<div className="p-2 space-y-1">
|
||||
{liste.length === 0 ? (
|
||||
<p className="px-2 py-3 text-xs text-slate-500">{gun} gün içinde yapılacak takipli ihale yok. Yıldızla takibe alın; burada geri sayım başlar.</p>
|
||||
) : liste.map(({ t, kalan }) => (
|
||||
<button key={t.id} type="button" onClick={() => setActiveTenderDetail?.(t)}
|
||||
className="w-full flex items-center justify-between gap-2 rounded-lg p-2 text-xs hover:bg-slate-50 text-left">
|
||||
<span className="min-w-0 truncate font-medium text-slate-800" title={t.baslik}>{t.baslik}</span>
|
||||
<span className={`shrink-0 rounded-md px-1.5 py-0.5 text-[10px] font-extrabold ${renk(kalan)}`}>{kalan === 0 ? 'BUGÜN' : `${kalan} gün`}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user