'use client'; import { useState } from 'react'; import { motion } from 'framer-motion'; import { fadeUp } from '@/lib/animations'; import { Flask as Beaker, WaveSine as Waves, Hexagon, Lightning as Zap, CaretDown as ChevronDown, CaretRight as ChevronRight, DownloadSimple as Download } from '@phosphor-icons/react'; import type { DockingInteraction } from '@/lib/api'; import { downloadTsv } from '@/lib/export-utils'; interface InteractionPanelProps { interactions: DockingInteraction; onHighlight?: (coords: [number, number, number] | null) => void; } function HbondTable({ data, onHighlight }: { data: DockingInteraction['hbonds']; onHighlight?: (c: [number, number, number] | null) => void }) { const [collapsed, setCollapsed] = useState(false); if (!data.length) return null; return (
} count={data.length} color="bg-interaction-hbond/20 text-interaction-hbond" collapsed={collapsed} onToggle={() => setCollapsed(!collapsed)}> {!collapsed && (
{data.map((item, i) => ( onHighlight?.(item.protein_coords)} onMouseLeave={() => onHighlight?.(null)}> ))}
ResidueAtomDistanceConfidence
{item.protein_residue}{item.protein_residue_seq}{item.protein_chain} {item.protein_atom} {item.distance.toFixed(2)}Å {item.confidence}
)}
); } function HydrophobicTable({ data, onHighlight }: { data: DockingInteraction['hydrophobic']; onHighlight?: (c: [number, number, number] | null) => void }) { const [collapsed, setCollapsed] = useState(false); if (!data.length) return null; return (
} count={data.length} color="bg-interaction-hydrophobic/20 text-interaction-hydrophobic" collapsed={collapsed} onToggle={() => setCollapsed(!collapsed)}> {!collapsed && (
{data.map((item, i) => ( onHighlight?.(item.protein_coords)} onMouseLeave={() => onHighlight?.(null)}> ))}
ResidueAtomDistance
{item.protein_residue}{item.protein_residue_seq}{item.protein_chain} {item.protein_atom} {item.distance.toFixed(2)}Å
)}
); } function PiStackTable({ data, onHighlight }: { data: DockingInteraction['pi_stacking']; onHighlight?: (c: [number, number, number] | null) => void }) { const [collapsed, setCollapsed] = useState(false); if (!data.length) return null; return (
} count={data.length} color="bg-interaction-pi/20 text-interaction-pi" collapsed={collapsed} onToggle={() => setCollapsed(!collapsed)}> {!collapsed && (
{data.map((item, i) => ( onHighlight?.(item.ring_centroid)} onMouseLeave={() => onHighlight?.(null)}> ))}
ResidueTypeDistanceAngleConfidence
{item.protein_residue}{item.protein_residue_seq}{item.protein_chain} {item.stacking_type} {item.distance.toFixed(2)}Å {item.angle.toFixed(1)}° {item.confidence}
)}
); } function SaltBridgeTable({ data, onHighlight }: { data: DockingInteraction['salt_bridges']; onHighlight?: (c: [number, number, number] | null) => void }) { const [collapsed, setCollapsed] = useState(false); if (!data.length) return null; return (
} count={data.length} color="bg-interaction-salt/20 text-interaction-salt" collapsed={collapsed} onToggle={() => setCollapsed(!collapsed)}> {!collapsed && (
{data.map((item, i) => ( onHighlight?.(item.protein_coords)} onMouseLeave={() => onHighlight?.(null)}> ))}
ResidueAtomDistanceCharge
{item.protein_residue}{item.protein_residue_seq}{item.protein_chain} {item.protein_atom} {item.distance.toFixed(2)}Å {item.charge_pair}
)}
); } function Section({ title, icon, count, color, collapsed, onToggle, children }: { title: string; icon: React.ReactNode; count: number; color: string; collapsed: boolean; onToggle: () => void; children: React.ReactNode; }) { return (
{children}
); } export function InteractionPanel({ interactions, onHighlight }: InteractionPanelProps) { const total = (interactions.hbonds?.length || 0) + (interactions.hydrophobic?.length || 0) + (interactions.pi_stacking?.length || 0) + (interactions.salt_bridges?.length || 0); if (!total) return null; const exportInteractions = () => { const rows: string[][] = []; interactions.hbonds.forEach((it) => rows.push(['hbond', it.protein_residue + it.protein_residue_seq + it.protein_chain, it.protein_atom, it.ligand_atom, it.distance.toFixed(2), it.confidence, ''])); interactions.hydrophobic.forEach((it) => rows.push(['hydrophobic', it.protein_residue + it.protein_residue_seq + it.protein_chain, it.protein_atom, it.ligand_atom, it.distance.toFixed(2), '', ''])); interactions.pi_stacking.forEach((it) => rows.push(['pi_stacking', it.protein_residue + it.protein_residue_seq + it.protein_chain, '', '', it.distance.toFixed(2), it.confidence, it.angle.toFixed(1)])); interactions.salt_bridges.forEach((it) => rows.push(['salt_bridge', it.protein_residue + it.protein_residue_seq + it.protein_chain, it.protein_atom, it.ligand_atom, it.distance.toFixed(2), '', it.charge_pair])); downloadTsv( ['Type', 'Residue', 'Protein atom', 'Ligand atom', 'Distance (A)', 'Confidence', 'Note'], rows, `docking_interactions_${total}.tsv`, ); }; return (

Interaction Fingerprint ({total} contacts)

); }