File size: 4,553 Bytes
40ad0f5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
116
117
118
119
120
121
122
123
124
125
126
127
128
import { RankedCandidate } from "../api";
import { useEffect, useMemo, useState } from "react";

export function CandidateTable({ candidates }: { candidates: RankedCandidate[] }) {
  const pageSize = 10;
  const [page, setPage] = useState(1);
  const [preview, setPreview] = useState<RankedCandidate | null>(null);
  const pageCount = Math.max(1, Math.ceil(candidates.length / pageSize));
  const visibleCandidates = useMemo(
    () => candidates.slice((page - 1) * pageSize, page * pageSize),
    [candidates, page],
  );

  useEffect(() => {
    setPage(1);
  }, [candidates]);

  return (
    <section className="results">
      <div className="results-heading">
        <div>
          <span className="eyebrow">Candidate intelligence</span>
          <h2>Ranked Candidates</h2>
        </div>
        <span className="count-chip">{candidates.length} structures</span>
      </div>
      <table>
        <thead>
          <tr>
            <th>Rank</th>
            <th>Structure</th>
            <th>SMILES</th>
            <th>Score</th>
            <th>Similarity</th>
            <th>Reason</th>
            <th>Warnings</th>
          </tr>
        </thead>
        <tbody>
          {candidates.length === 0 ? (
            <tr>
              <td className="empty-state" colSpan={7}>
                No ranked candidates yet.
              </td>
            </tr>
          ) : (
            visibleCandidates.map((item) => (
              <tr key={item.canonical_smiles}>
                <td>{item.rank}</td>
                <td>
                  <button
                    type="button"
                    aria-label={`Enlarge structure for candidate ${item.rank}`}
                    className="structure-depiction structure-button"
                    onClick={() => setPreview(item)}
                    dangerouslySetInnerHTML={{ __html: item.structure_svg }}
                  />
                </td>
                <td className="mono">{item.canonical_smiles}</td>
                <td>{item.score}</td>
                <td>{item.similarity}</td>
                <td>{item.recommendation}</td>
                <td>{item.warnings.join("; ")}</td>
              </tr>
            ))
          )}
        </tbody>
      </table>
      {candidates.length > 0 && (
        <nav className="pagination" aria-label="Candidate pagination">
          <button disabled={page === 1} onClick={() => setPage((current) => Math.max(1, current - 1))}>
            Previous page
          </button>
          <span>
            Page {page} of {pageCount}
          </span>
          <button disabled={page === pageCount} onClick={() => setPage((current) => Math.min(pageCount, current + 1))}>
            Next page
          </button>
        </nav>
      )}
      {preview && (
        <div className="image-modal-backdrop" role="presentation" onClick={() => setPreview(null)}>
          <div
            aria-label={`Candidate ${preview.rank} structure comparison`}
            aria-modal="true"
            className="image-modal"
            role="dialog"
            onClick={(event) => event.stopPropagation()}
          >
            <div className="results-heading">
              <div>
                <span className="eyebrow">Structure preview</span>
                <h3>Candidate {preview.rank}</h3>
              </div>
              <button type="button" onClick={() => setPreview(null)}>
                Close
              </button>
            </div>
            <div className="comparison-grid">
              <section>
                <h4>Original structure</h4>
                <div
                  aria-label={`Original structure comparison for candidate ${preview.rank}`}
                  className="structure-preview"
                  dangerouslySetInnerHTML={{ __html: preview.reference_structure_svg }}
                />
              </section>
              <section>
                <h4>Modified candidate</h4>
                <div
                  aria-label={`Changed candidate atoms highlighted for candidate ${preview.rank}`}
                  className="structure-preview"
                  dangerouslySetInnerHTML={{ __html: preview.candidate_change_svg }}
                />
              </section>
            </div>
            <p className="change-note">
              Changed atoms are highlighted in orange on the modified candidate. {preview.change_summary}
            </p>
            <p className="mono">{preview.canonical_smiles}</p>
          </div>
        </div>
      )}
    </section>
  );
}