"use client"; import { useEffect, useState } from "react"; type SSResidue = { position: number; residue: string; ss: string; source: string }; const SS_COLOR: Record = { H: "#4ADE80", E: "#7C3AED", C: "rgba(255,255,255,0.15)" }; const SS_LABEL: Record = { H: "α-Helix", E: "β-Sheet", C: "Coil" }; export function SecondaryStructureViewer({ identifier }: { identifier: string }) { const [data, setData] = useState<{ method: string; residues: SSResidue[] } | null>(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { setLoading(true); setError(null); fetch(`/api/backend/api/structure_analysis/secondary_structure/${identifier}`) .then(r => { if (!r.ok) return r.json().then(e => Promise.reject(new Error(e.detail || `Status ${r.status}`))); return r.json(); }) .then(setData) .catch(e => setError(e.message)) .finally(() => setLoading(false)); }, [identifier]); if (loading) return
Predicting secondary structure…
; if (error) return
{error}
; if (!data) return
Failed to load.
; const { residues, method } = data; const CHUNK = 50; const rows: SSResidue[][] = []; for (let i = 0; i < residues.length; i += CHUNK) rows.push(residues.slice(i, i + CHUNK)); const counts = { H: residues.filter(r => r.ss === "H").length, E: residues.filter(r => r.ss === "E").length, C: residues.filter(r => r.ss === "C").length, }; return (

Secondary Structure

{method}
{(["H", "E", "C"] as const).map(ss => (
))}
{(["H", "E", "C"] as const).map(ss => ( {SS_LABEL[ss]} {((counts[ss] / residues.length) * 100).toFixed(0)}% ))}
{rows.map((row, ri) => (
{row.map((r, i) => (
))}
{row.map((r, i) => ( {r.residue} ))}
{row[0].position}
))}
); }