| import React, { useEffect, useState } from 'react'; |
| import { proteinApi } from '../../../api/protein'; |
|
|
| interface FunctionPanelProps { |
| pdbId: string; |
| chainId: string; |
| } |
|
|
| const FunctionPanel: React.FC<FunctionPanelProps> = ({ pdbId, chainId }) => { |
| const [functions, setFunctions] = useState<string[] | null>(null); |
| const [loading, setLoading] = useState(true); |
| const [notFound, setNotFound] = useState(false); |
|
|
| useEffect(() => { |
| let cancelled = false; |
| setLoading(true); |
| setNotFound(false); |
| setFunctions(null); |
|
|
| proteinApi.getChainFunctions(pdbId, chainId) |
| .then(fns => { if (!cancelled) setFunctions(fns); }) |
| .catch(() => { if (!cancelled) setNotFound(true); }) |
| .finally(() => { if (!cancelled) setLoading(false); }); |
|
|
| return () => { cancelled = true; }; |
| }, [pdbId, chainId]); |
|
|
| return ( |
| <div className="border border-gray-200 rounded-2xl bg-white shadow-sm hover:border-slate-900 hover:shadow-lg hover:-translate-y-0.5 transition-all duration-300"> |
| {/* Header */} |
| <div className="flex items-center justify-between px-6 py-4 border-b border-gray-100"> |
| <h3 className="text-xl font-semibold text-slate-900">Ranked Functions</h3> |
| {functions && ( |
| <span className="text-sm text-slate-500"> |
| {functions.length} annotation{functions.length !== 1 ? 's' : ''} |
| </span> |
| )} |
| </div> |
| |
| {/* Scrollable body */} |
| <div className="px-6 py-4" style={{ overflowY: 'auto', overflowX: 'hidden', maxHeight: '14rem' }}> |
| {loading && ( |
| <p className="text-sm text-slate-500 text-center py-4">Loading functions…</p> |
| )} |
| |
| {!loading && (notFound || (functions && functions.length === 0)) && ( |
| <p className="text-sm text-slate-500 text-center py-4">No functional annotation available</p> |
| )} |
| |
| {!loading && functions && functions.length > 0 && ( |
| <ol className="space-y-3"> |
| {functions.map((fn, idx) => ( |
| <li key={idx} className="flex gap-3 text-sm text-slate-700" style={{ minWidth: 0 }}> |
| <span className="shrink-0 mt-0.5 w-5 h-5 rounded-full bg-slate-100 text-slate-500 text-[10px] font-semibold flex items-center justify-center"> |
| {idx + 1} |
| </span> |
| <span style={{ flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{fn}</span> |
| </li> |
| ))} |
| </ol> |
| )} |
| </div> |
| </div> |
| ); |
| }; |
|
|
| export default FunctionPanel; |
|
|