'use client'; import React, { useCallback, useEffect, useState } from 'react'; import { Loader2, Minus, Network, Plus, X } from 'lucide-react'; import { API_BASE } from '@/lib/api'; import { isEntityGraphEligible, mapEntityToGraphType } from '@/lib/entityGraph'; import type { SelectedEntity } from '@/types/dashboard'; interface GraphNode { id: string; label: string; type: string; properties?: Record; } interface GraphLink { source: string; target: string; label: string; } interface Props { entity: SelectedEntity | null; onClose: () => void; } const TYPE_COLORS: Record = { aircraft: 'text-cyan-300', vessel: 'text-cyan-400', company: 'text-amber-300', person: 'text-violet-300', country: 'text-emerald-300', sanction: 'text-red-300', ip: 'text-orange-300', event: 'text-yellow-300', }; export default function EntityGraphPanel({ entity, onClose }: Props) { const [isMinimized, setIsMinimized] = useState(false); const [nodes, setNodes] = useState([]); const [links, setLinks] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const loadGraph = useCallback(async () => { if (!entity || !isEntityGraphEligible(entity)) return; const type = mapEntityToGraphType(entity.type); if (!type) return; const id = String(entity.name || entity.extra?.callsign || entity.extra?.registration || entity.id); const params = new URLSearchParams({ type, id }); if (entity.extra?.registration) params.set('registration', String(entity.extra.registration)); if (entity.extra?.icao24) params.set('icao24', String(entity.extra.icao24)); if (entity.extra?.model) params.set('model', String(entity.extra.model)); setLoading(true); setError(null); try { const res = await fetch(`${API_BASE}/api/entity/expand?${params}`); const data = await res.json(); if (!res.ok) throw new Error(data.detail || data.error || 'Expand failed'); setNodes(data.nodes || []); setLinks(data.links || []); } catch (err) { setError(err instanceof Error ? err.message : 'Graph unavailable'); setNodes([]); setLinks([]); } finally { setLoading(false); } }, [entity]); useEffect(() => { if (entity) loadGraph(); else { setNodes([]); setLinks([]); } }, [entity, loadGraph]); if (!entity || !isEntityGraphEligible(entity)) return null; return (
setIsMinimized((prev) => !prev)} >
ENTITY GRAPH
{isMinimized ? ( ) : ( )}
{!isMinimized && (
{entity.type.toUpperCase()} · {entity.name || entity.id}
{loading && (
RESOLVING…
)} {error && (
{error}
)} {!loading && !error && ( <>
{nodes.map((n) => (
{n.type}
{n.label}
))}
{links.length > 0 && (
RELATIONSHIPS
{links.slice(0, 24).map((l, i) => (
{l.label}: {l.source.split(':').pop()} → {l.target.split(':').pop()}
))}
)} )}
)}
); }