Spaces:
Running
Running
File size: 5,752 Bytes
5a0e691 2f67ecf 349f22c 5a0e691 2f67ecf 5a0e691 9a28cd0 5a0e691 973dd4c 9a28cd0 fcb1364 5a0e691 5e98b5a 5a0e691 2a12412 5a0e691 2a12412 5a0e691 fcb1364 5a0e691 2a12412 5a0e691 2a12412 5a0e691 2a12412 5a0e691 864aa53 5a0e691 2a12412 5a0e691 2a12412 5a0e691 2a12412 5a0e691 2a12412 5a0e691 864aa53 2f67ecf 5a0e691 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | 'use client';
import { motion } from 'framer-motion';
import { ArrowSquareOut as ExternalLink, Cube as Box } from '@phosphor-icons/react';
import type { UniprotSummary } from '@/types/pipeline';
import { fadeUp, cardHover } from '@/lib/animations';
interface UniprotPanelProps {
data: UniprotSummary | null;
}
export function UniprotPanel({ data }: UniprotPanelProps) {
if (!data) return null;
const isUniprot = /^[OPQ][0-9][A-Z0-9]{3}[0-9]$|^A0A[A-Z0-9]{5,}[0-9]$/.test(data.accession);
const isNcbi = /^([NXYP]P_|[NXYP]M_|XM_|XR_|NR_)/.test(data.accession);
return (
<motion.div variants={fadeUp} whileHover={cardHover} className="data-card p-6">
<div className="flex items-start justify-between mb-4">
<div>
<h3 className="font-medium text-text-primary">{data.full_name}</h3>
<div className="flex items-center gap-3 mt-1">
<span className="font-mono text-xs bg-accent-cyan/10 text-accent-cyan px-2 py-0.5 rounded">{data.accession}</span>
<span className="text-sm text-text-secondary">{data.organism}</span>
</div>
</div>
{isUniprot && (
<a href={`https://www.uniprot.org/uniprotkb/${data.accession}`} target="_blank" rel="noopener noreferrer" className="text-sm text-accent-cyan hover:text-accent-cyan/80 transition flex items-center gap-1">
UniProt <ExternalLink className="w-3.5 h-3.5" />
</a>
)}
{!isUniprot && isNcbi && (
<a href={`https://www.ncbi.nlm.nih.gov/protein/${data.accession}`} target="_blank" rel="noopener noreferrer" className="text-sm text-accent-cyan hover:text-accent-cyan/80 transition flex items-center gap-1">
NCBI <ExternalLink className="w-3.5 h-3.5" />
</a>
)}
</div>
<div className="grid md:grid-cols-2 gap-4">
<div>
{data.gene_names?.length > 0 && (
<div className="mb-3">
<h4 className="text-xs font-semibold text-text-muted uppercase tracking-wider mb-1">Gene</h4>
<div className="flex flex-wrap gap-1">{data.gene_names.map(g => <span key={g} className="text-sm bg-surface-1 px-2 py-0.5 rounded font-mono text-text-secondary">{g}</span>)}</div>
</div>
)}
{data.subcellular_locations?.length > 0 && (
<div>
<h4 className="text-xs font-semibold text-text-muted uppercase tracking-wider mb-1">Location</h4>
<div className="flex flex-wrap gap-1">{data.subcellular_locations.map(loc => <span key={loc} className="text-sm bg-accent-purple/10 text-accent-purple px-2 py-0.5 rounded">{loc}</span>)}</div>
</div>
)}
</div>
<div>
{data.keywords?.length > 0 && (
<div>
<h4 className="text-xs font-semibold text-text-muted uppercase tracking-wider mb-1">Keywords</h4>
<div className="flex flex-wrap gap-1">{data.keywords.map(kw => <span key={kw} className="text-xs bg-surface-1 text-text-muted px-2 py-0.5 rounded">{kw}</span>)}</div>
</div>
)}
</div>
</div>
{data.pdb_ids?.length > 0 && (
<div className="mt-4 pt-4 border-t border-glass-border">
<h4 className="text-xs font-semibold text-text-muted uppercase tracking-wider mb-2 flex items-center gap-1.5">
<Box className="w-3.5 h-3.5" /> PDB Structures ({data.pdb_ids.length})
</h4>
<div className="flex flex-wrap gap-2">
{data.pdb_ids.slice(0, 12).map(pdb => (
<a key={pdb} href={`https://www.rcsb.org/structure/${pdb}`} target="_blank" rel="noopener noreferrer"
className="text-xs font-mono bg-accent-cyan/10 text-accent-cyan px-2 py-1 rounded hover:bg-accent-cyan/20 transition flex items-center gap-1">
{pdb} <ExternalLink className="w-3 h-3" />
</a>
))}
{data.pdb_ids.length > 12 && <span className="text-xs text-text-muted px-2 py-1">+{data.pdb_ids.length - 12} more</span>}
</div>
</div>
)}
{data.functions?.length > 0 && (
<div className="mt-4 pt-4 border-t border-glass-border">
<h4 className="text-xs font-semibold text-text-muted uppercase tracking-wider mb-2">Function</h4>
{data.functions.map((f, fi) => <p key={fi} className="text-sm text-text-secondary leading-relaxed mb-1">{f}</p>)}
</div>
)}
{data.features?.length > 0 && (
<div className="mt-4 pt-4 border-t border-glass-border">
<h4 className="text-xs font-semibold text-text-muted uppercase tracking-wider mb-2">Active Sites & Features</h4>
<div className="space-y-1">
{data.features.map((f, fi) => (
<div key={fi} className="text-sm text-text-secondary">
<span className="font-medium">{f.type}:</span> {f.description}
{(f.begin || f.end) && <span className="text-text-muted"> ({f.begin}–{f.end})</span>}
</div>
))}
</div>
</div>
)}
{data.go_terms?.length > 0 && (
<div className="mt-4 pt-4 border-t border-glass-border">
<h4 className="text-xs font-semibold text-text-muted uppercase tracking-wider mb-2">GO Terms</h4>
<div className="flex flex-wrap gap-1.5">
{data.go_terms.slice(0, 15).map((go, i) => (
<span key={i} className="text-xs bg-surface-1 text-text-muted px-2 py-0.5 rounded font-mono">{go}</span>
))}
{data.go_terms.length > 15 && <span className="text-xs text-text-muted">+{data.go_terms.length - 15} more</span>}
</div>
</div>
)}
</motion.div>
);
}
|