MuSProt / frontend /src /components /datasets /protein /ChainBasicInfoCards.tsx
WinslowFan's picture
Functions: only show strings longer than 10 chars; remove chain composition from UI
6d84fd1
Raw
History Blame Contribute Delete
6.39 kB
/**
* Basic information card for a selected chain
* Displays: PDB ID, Protein name, Chain ID, UniProt ID in a single well-designed card
*/
import { ChainMetadata } from '../../../types/protein';
interface ChainBasicInfoCardsProps {
metadata: ChainMetadata | null;
isLoading?: boolean;
}
const ChainBasicInfoCards: React.FC<ChainBasicInfoCardsProps> = ({ metadata, isLoading }) => {
const getUniProtId = () => {
if (!metadata?.polymer_entity?.reference_sequences) return 'β€”';
const refSeqs = metadata.polymer_entity.reference_sequences;
const uniprotRefs = refSeqs.filter((ref: any) => ref.database_name === 'UniProt');
if (uniprotRefs.length > 0) {
const first = uniprotRefs[0].database_accession;
if (uniprotRefs.length > 1) {
return (
<span title={uniprotRefs.map((r: any) => r.database_accession).join(', ')}>
{first} <span className="text-gray-500 text-xs">+{uniprotRefs.length - 1} more</span>
</span>
);
}
return first;
}
// Fallback to first reference sequence
if (refSeqs.length > 0) {
return refSeqs[0].database_accession || 'β€”';
}
return 'β€”';
};
if (isLoading) {
return (
<div className="border border-gray-200 rounded-2xl p-6 bg-white shadow-sm">
<div className="flex items-center gap-3">
<div className="w-8 h-8 border-4 border-slate-200 border-t-slate-900 rounded-full animate-spin"></div>
<span className="text-slate-600 text-sm">Loading chain metadata...</span>
</div>
</div>
);
}
const pdbId = metadata?.entry?.rcsb_id || metadata?.pdb_id;
const chainId = metadata?.chain_id || metadata?.polymer_entity_instance?.auth_asym_id;
return (
<div className="h-full w-full border border-gray-200 rounded-2xl p-6 bg-white shadow-sm hover:border-slate-900 hover:shadow-lg hover:-translate-y-0.5 transition-all duration-300">
<div className="flex items-start justify-between mb-4">
<h3 className="text-xl font-semibold text-slate-900">Chain Metadata</h3>
<div className="flex flex-wrap gap-2">
<span className="px-3 py-1.5 bg-blue-50 text-blue-700 text-xs font-medium rounded-full border border-blue-200">
{pdbId || 'N/A'}
</span>
{chainId && (
<span className="px-3 py-1.5 bg-purple-50 text-purple-700 text-xs font-medium rounded-full border border-purple-200">
Chain {chainId}
</span>
)}
</div>
</div>
{/* Chips Row */}
{/* Compact three-column metadata flow */}
<div className="gap-4" style={{ columnCount: 3, columnGap: '1rem' }}>
<div className="break-inside-avoid mb-3" style={{ breakInside: 'avoid' }}>
<div className="text-xs font-medium text-slate-500 uppercase tracking-wider mb-1">Protein Name</div>
<div className="text-sm font-semibold text-slate-900 font-mono" title={metadata?.entry?.title || ''}>
{metadata?.entry?.title || 'β€”'}
</div>
</div>
<div className="break-inside-avoid mb-3" style={{ breakInside: 'avoid' }}>
<div className="text-xs font-medium text-slate-500 uppercase tracking-wider mb-1">PDB ID</div>
<div className="text-sm font-semibold text-slate-900">
<a
href={`https://www.rcsb.org/structure/${pdbId}`}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800 font-mono hover:underline inline-flex items-center gap-1 transition-colors"
>
{pdbId || 'β€”'}
<span className="text-xs">β†—</span>
</a>
</div>
</div>
<div className="break-inside-avoid mb-3" style={{ breakInside: 'avoid' }}>
<div className="text-xs font-medium text-slate-500 uppercase tracking-wider mb-1">Chain ID</div>
<div className="text-sm font-semibold text-slate-900 font-mono">{chainId || 'β€”'}</div>
</div>
<div className="break-inside-avoid mb-3" style={{ breakInside: 'avoid' }}>
<div className="text-xs font-medium text-slate-500 uppercase tracking-wider mb-1">UniProt ID</div>
<div className="text-sm font-semibold text-slate-900 font-mono">{getUniProtId()}</div>
</div>
<div className="break-inside-avoid mb-3" style={{ breakInside: 'avoid' }}>
<div className="text-xs font-medium text-slate-500 uppercase tracking-wider mb-1">Binding Status</div>
<div className="text-sm font-semibold text-slate-900 capitalize">{metadata?.binding_status || 'β€”'}</div>
</div>
<div className="break-inside-avoid mb-3" style={{ breakInside: 'avoid' }}>
<div className="text-xs font-medium text-slate-500 uppercase tracking-wider mb-1">CATH ID</div>
<div className="text-sm font-semibold text-slate-900 font-mono">{metadata?.cath_id || 'uncategorized'}</div>
</div>
<div className="break-inside-avoid mb-3" style={{ breakInside: 'avoid' }}>
<div className="text-xs font-medium text-slate-500 uppercase tracking-wider mb-1">CATH Domain</div>
<div className="text-sm font-semibold text-slate-900 font-mono">
{metadata?.cath_superfamily ? (
<a
href={`https://www.cathdb.info/version/latest/superfamily/${metadata.cath_superfamily}`}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800 hover:underline inline-flex items-center gap-1 transition-colors"
>
{metadata.cath_superfamily}
<span className="text-xs">β†—</span>
</a>
) : 'β€”'}
</div>
</div>
<div className="break-inside-avoid mb-3" style={{ breakInside: 'avoid' }}>
<div className="text-xs font-medium text-slate-500 uppercase tracking-wider mb-1">State ID</div>
<div className="text-sm font-semibold text-slate-900 font-mono">{metadata?.state_id || 'β€”'}</div>
</div>
</div>
</div>
);
};
export default ChainBasicInfoCards;