import StatCard from "./StatCard"; import { Tender } from "../lib/types"; import { useEffect, useMemo, useState } from "react"; import BrandLoader from "./BrandLoader"; import { searchTenders, fetchDbStatus, syncDatabase, fetchRecommendations } from "../lib/api"; import { translations, Language } from "../lib/translations"; type Props = { tendersFound: number; recommendedOpportunities: number; highRiskItems: number; reportsGenerated: number; followedTendersCount: number; tenders: Tender[]; onFilterClick?: (type: "sector" | "region", value: string) => void; onTenderClick?: (tender: Tender) => void; lang: Language; }; export default function Dashboard({ tendersFound, recommendedOpportunities, highRiskItems, reportsGenerated, followedTendersCount, tenders, onFilterClick, onTenderClick, lang }: Props) { const t = translations[lang]; const [isSyncing, setIsSyncing] = useState(false); const [dbStatus, setDbStatus] = useState(null); const [recommendations, setRecommendations] = useState([]); const [loadingRecs, setLoadingRecs] = useState(true); useEffect(() => { async function loadRecs() { console.log("[Dashboard] Fetching IA Recommendations..."); setLoadingRecs(true); try { const recs = await fetchRecommendations(); console.log(`[Dashboard] Received ${recs.length} recommendations`); setRecommendations(recs); } catch (err) { console.error("[Dashboard] Failed to fetch recommendations", err); } finally { setLoadingRecs(false); } } loadRecs(); }, []); useEffect(() => { async function loadStatus() { const status = await fetchDbStatus(); setDbStatus(status); } loadStatus(); }, [tenders]); const handleGlobalSync = async () => { setIsSyncing(true); try { await syncDatabase(); await new Promise(r => setTimeout(r, 1500)); window.location.reload(); } catch (e) { console.error(e); } finally { setIsSyncing(false); } }; const sectorDistribution = useMemo(() => { const counts: Record = {}; tenders.forEach(t => { const sector = t.sector || "General"; counts[sector] = (counts[sector] || 0) + 1; }); return Object.entries(counts) .sort((a, b) => b[1] - a[1]) .slice(0, 5); }, [tenders]); const regionDistribution = useMemo(() => { const counts: Record = {}; tenders.forEach(t => { const region = t.region || "Sin Región"; counts[region] = (counts[region] || 0) + 1; }); return Object.entries(counts) .sort((a, b) => b[1] - a[1]) .slice(0, 5); }, [tenders]); const deadlineStatus = useMemo(() => { const now = new Date(); const status = { urgent: 0, near: 0, far: 0 }; tenders.forEach(t => { if (!t.closing_date) return; const closing = new Date(t.closing_date); const diffDays = Math.ceil((closing.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)); if (diffDays < 7) status.urgent++; else if (diffDays < 21) status.near++; else status.far++; }); return status; }, [tenders]); const totalAmount = useMemo(() => { return tenders.reduce((acc, t) => acc + (t.estimated_amount || 0), 0); }, [tenders]); const formatAmount = (amount: number) => { if (amount >= 1_000_000_000) { return `$${(amount / 1_000_000_000).toFixed(1)}B`; } if (amount >= 1_000_000) { return `$${(amount / 1_000_000).toFixed(1)}M`; } return new Intl.NumberFormat("es-CL", { style: "currency", currency: "CLP", maximumFractionDigits: 0 }).format(amount); }; return (
{isSyncing && }

{t.resumenEjecutivo}

AndesOps AI

{t.andesOpsDesc}

0 ? recommendations.length : recommendedOpportunities} subtitle="AI Matched" />
{/* Sector Distribution */}

{t.sectors}

{sectorDistribution.length > 0 ? ( sectorDistribution.map(([sector, count]) => ( )) ) : (

Sin datos disponibles.

)}
{/* Region Distribution */}

{t.regionalDist}

{regionDistribution.length > 0 ? ( regionDistribution.map(([region, count]) => ( )) ) : (

Sin datos disponibles.

)}
{/* Deadline Status - Enhanced Visual */}

{t.deadlines}

{/* Complex Radial Background with Multiple Segments via CSS Gradients */}
{tenders.length}
Total
Urgent
{deadlineStatus.urgent}
Near
{deadlineStatus.near}
Safe
{deadlineStatus.far}
{/* Database Status Table (New) */}

{t.integrityMonitor}

{dbStatus?.top_buyers?.map((b: any, i: number) => ( ))} {!dbStatus?.top_buyers?.length && ( )}
Organismo Local Qty
{b.name} {b.count}
No local data found.
Total Local Tenders: {dbStatus?.total_records || 0}
Last Pulse: {dbStatus?.last_sync ? new Date(dbStatus.last_sync).toLocaleTimeString() : 'Never'}
🤖

IA Recommendations for your Company

{(recommendations.length > 0 || tenders.length > 0) ? ( (recommendations.length > 0 ? recommendations : tenders).slice(0, 6).map((t) => ( // ... existing map logic ...
onTenderClick?.(t)} className="flex items-center justify-between p-4 rounded-2xl bg-slate-900/40 border border-slate-800/50 hover:bg-slate-900/60 transition group cursor-pointer" >
{t.sector?.charAt(0) || "T"}
{t.name}
{t.buyer}
Región
{t.region || "N/A"}
Código
{t.code}
)) ) : (
📡

No local data found yet. Sync with Mercado Público to feed the Intelligence Pipeline.

)}
); }