'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 && (
| Residue | Atom | Distance | Confidence |
{data.map((item, i) => (
onHighlight?.(item.protein_coords)}
onMouseLeave={() => onHighlight?.(null)}>
| {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 && (
| Residue | Atom | Distance |
{data.map((item, i) => (
onHighlight?.(item.protein_coords)}
onMouseLeave={() => onHighlight?.(null)}>
| {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 && (
| Residue | Type | Distance | Angle | Confidence |
{data.map((item, i) => (
onHighlight?.(item.ring_centroid)}
onMouseLeave={() => onHighlight?.(null)}>
| {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 && (
| Residue | Atom | Distance | Charge |
{data.map((item, i) => (
onHighlight?.(item.protein_coords)}
onMouseLeave={() => onHighlight?.(null)}>
| {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)
);
}