'use client'; import { useEffect, useState } from 'react'; import { useParams } from 'next/navigation'; import Link from 'next/link'; import { FiArrowLeft, FiExternalLink, FiDownload, FiLoader } from 'react-icons/fi'; import type { DiscoveryResult, TechnologyCandidate, DeepDiveResult } from '../../types'; export default function DiscoveryDetailPage() { const params = useParams(); const [discovery, setDiscovery] = useState(null); const [loading, setLoading] = useState(true); const [selectedCandidate, setSelectedCandidate] = useState(null); const [deepDiveLoading, setDeepDiveLoading] = useState(false); const [deepDiveResult, setDeepDiveResult] = useState(null); useEffect(() => { if (params.id) { fetch(`/api/discoveries/${params.id}`) .then(res => res.json()) .then(data => { setDiscovery(data); setLoading(false); }) .catch(err => { console.error('Failed to load discovery:', err); setLoading(false); }); } }, [params.id]); const handleExport = () => { if (!discovery) return; // Create CSV content const headers = ['Rank', 'Title', 'Organization', 'Score', 'TRL', 'Source', 'Award Amount', 'URL']; const rows = discovery.candidates.map((c, i) => [ i + 1, `"${c.title.replace(/"/g, '""')}"`, `"${c.organization.replace(/"/g, '""')}"`, (c.score * 100).toFixed(1) + '%', c.trl_estimate || 'N/A', c.source_type, c.award_amount ? `$${c.award_amount.toLocaleString()}` : 'N/A', c.url ]); const csv = [headers.join(','), ...rows.map(r => r.join(','))].join('\n'); // Download const blob = new Blob([csv], { type: 'text/csv' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `techscout_${discovery.id}_results.csv`; a.click(); URL.revokeObjectURL(url); }; const handleDeepDive = async (candidate: TechnologyCandidate) => { if (!candidate || !discovery) return; setDeepDiveLoading(true); setDeepDiveResult(null); try { const response = await fetch('http://localhost:8000/api/deepdive', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ organization: candidate.organization, technology: candidate.description, gap: discovery.capability_gap, }), }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || 'Deep dive failed'); } const data = await response.json(); if (data.error) { throw new Error(data.error); } if (data.result) { setDeepDiveResult(data.result); } } catch (error) { console.error('Deep dive failed:', error); alert(`Deep dive failed: ${error}`); } finally { setDeepDiveLoading(false); } }; if (loading) { return (
Loading discovery...
); } if (!discovery) { return (

Discovery not found

Back to discoveries
); } return (
{/* Header */}
Back to discoveries

{discovery.capability_gap}

{new Date(discovery.timestamp).toLocaleString()} {discovery.search_duration_seconds.toFixed(1)}s {discovery.total_results_found} total results
{/* Query Analysis */}

Query Analysis

{discovery.decomposition.understanding}

{discovery.decomposition.technical_domains.map((domain, i) => ( {domain} ))}
{/* Results */}
{/* Candidate List */}

Technology Candidates ({discovery.candidates.length})

{discovery.candidates.map((candidate, i) => (
setSelectedCandidate(candidate)} className={`bg-[#12121a] rounded-xl border p-4 cursor-pointer transition-all ${ selectedCandidate?.id === candidate.id ? 'border-[#00d4ff] glow-accent' : 'border-[#1e1e2e] hover:border-[#2e2e3e]' }`} >
#{i + 1} {candidate.source_type}
{(candidate.score * 100).toFixed(0)}%
{candidate.trl_estimate && (
TRL {candidate.trl_estimate}
)}

{candidate.title}

{candidate.organization}

{candidate.award_amount && (

${candidate.award_amount.toLocaleString()}

)}
))}
{/* Selected Candidate Detail */} {selectedCandidate && (

{selectedCandidate.title}

{selectedCandidate.organization}

{selectedCandidate.description}

{(selectedCandidate.score * 100).toFixed(1)}%

{selectedCandidate.trl_estimate || 'Unknown'}

{selectedCandidate.source_type}

{selectedCandidate.published_date || 'N/A'}

{selectedCandidate.award_amount && (

${selectedCandidate.award_amount.toLocaleString()}

)}
View Source
)}
{/* Deep Dive Modal */} {deepDiveResult && ( setDeepDiveResult(null)} /> )}
); } function StatCard({ label, value, color }: { label: string; value: number; color: string }) { return (
{value}
{label}
); } function DeepDiveModal({ result, onClose }: { result: DeepDiveResult; onClose: () => void; }) { return (
{/* Header */}

{result.organization}

Deep Dive Analysis

{/* Recommendation Banner */}
{result.recommendation}
{/* Assessment */}

{result.assessment}

{/* Company Profile */}

{result.company_profile.description}

{result.company_profile.leadership.length > 0 && (
{result.company_profile.leadership.map((leader, i) => ( {leader.name} - {leader.title} ))}
)}
{/* Technology Profile */}

{result.technology_profile.technical_approach}

{result.technology_profile.competitive_advantage}

{result.technology_profile.key_capabilities.length > 0 && (
    {result.technology_profile.key_capabilities.map((cap, i) => (
  • {cap}
  • ))}
)} {result.technology_profile.limitations.length > 0 && (
    {result.technology_profile.limitations.map((lim, i) => (
  • {lim}
  • ))}
)}
{/* Contract History */}
{result.contract_history.total_contracts}
Total Contracts
${(result.contract_history.total_value / 1000000).toFixed(1)}M
Total Value
{result.contract_history.sbir_awards.length}
SBIR Awards
{result.contract_history.primary_agencies.length > 0 && (
{result.contract_history.primary_agencies.map((agency, i) => ( {agency} ))}
)}
{/* Risk Factors */} {result.risk_factors.length > 0 && (
    {result.risk_factors.map((risk, i) => (
  • ! {risk}
  • ))}
)} {/* News Mentions */} {result.news_mentions.length > 0 && (
{result.news_mentions.slice(0, 5).map((news, i) => (
{news.title}
{news.date}
))}
)}
); } function Section({ title, children }: { title: string; children: React.ReactNode }) { return (

{title}

{children}
); } function InfoItem({ label, value, isLink }: { label: string; value: unknown; isLink?: boolean }) { if (!value) return null; // Convert value to string, handling objects let displayValue: string; if (typeof value === 'object' && value !== null) { const obj = value as Record; // Handle common object shapes like {location: "..."} if (obj.location) { displayValue = String(obj.location); } else if (obj.name) { displayValue = String(obj.name); } else if (obj.value) { displayValue = String(obj.value); } else { // Fallback to JSON for unknown object shapes displayValue = JSON.stringify(value); } } else { displayValue = String(value); } return (
{isLink ? ( {displayValue} ) : (

{displayValue}

)}
); }