SPB Git forge

spb/cancerindex

Public
37commits 1branches 0releases
2.9 MBsize
maindefault branch
10 days agolast push
TypeScript 97.2% SQL 1.5% CSS 0.6% JavaScript 0.5%
7.5 KB · 185 lines tsx
Raw Blame History
1import Link from 'next/link';2import { Section } from '@/components/ui/section';3import { EmptyState } from '@/components/ui/empty-state';4import { Badge, MatchBadge } from '@/components/ui/badge';5import { JsonView } from '@/components/ui/json-view';6import { getContributingSources, getChangeEvents } from '@/lib/queries/cancers';7import { CODE_SYSTEM_LABEL, codeUrl } from '@/lib/site';8import { fmtDate, fmtDateTime, humanize } from '@/lib/format';9import type { CancerBundle } from '../load';1011export async function SourcesTab({ b }: { b: CancerBundle }) {12  const [contrib, events] = await Promise.all([getContributingSources(b.cancer.id, b.descendants), getChangeEvents('cancer', b.cancer.id)]);13  return (14    <div className="space-y-8">15      <Section id="contributing" kicker="Provenance" title="Sources contributing to this entity" description="Every source that supplied a code, alias, edge, observation or derived value, with the ingest runs involved.">16        {contrib.length ? (17          <div className="ci-table-wrap">18            <table className="ci-table">19              <thead>20                <tr>21                  <th>Source</th>22                  <th>License</th>23                  <th>Contributions</th>24                  <th>Ingest runs</th>25                  <th>Last retrieved</th>26                </tr>27              </thead>28              <tbody>29                {contrib.map((s) => (30                  <tr key={s.slug}>31                    <td>32                      <Link className="ci-link" href={`/source/${s.slug}`}>33                        {s.name}34                      </Link>{' '}35                      <span className="ci-mono text-[11px] text-ink-3">{s.slug}</span>36                    </td>37                    <td>38                      <Badge tone={s.license_status === 'approved' ? 'ok' : 'warn'}>{s.license_status}</Badge>39                    </td>40                    <td className="text-[12.5px]">{s.kinds.map(humanize).join(', ')}</td>41                    <td className="text-[11.5px]">42                      {s.run_ids.length ? (43                        s.run_ids.slice(0, 5).map((r) => (44                          <Link key={r} href={`/admin/runs/${r}`} className="ci-mono ci-link block">45                            {r}46                          </Link>47                        ))48                      ) : (49                        <span className="text-ink-4">—</span>50                      )}51                      {s.run_ids.length > 5 ? <span className="text-ink-3">+{s.run_ids.length - 5} more</span> : null}52                    </td>53                    <td className="whitespace-nowrap text-[12.5px]">{s.last_retrieved ? fmtDateTime(s.last_retrieved) : '—'}</td>54                  </tr>55                ))}56              </tbody>57            </table>58          </div>59        ) : (60          <EmptyState compact>No source contribution recorded.</EmptyState>61        )}62      </Section>6364      <Section id="codes" kicker="Identifiers" title={`Cross-reference codes (${b.codes.length})`} description="Every upstream identifier is kept with its match type; nothing is buried in JSON.">65        {b.codes.length ? (66          <div className="ci-table-wrap">67            <table className="ci-table">68              <thead>69                <tr>70                  <th>System</th>71                  <th>Code</th>72                  <th>Match type</th>73                  <th>Source</th>74                </tr>75              </thead>76              <tbody>77                {b.codes.map((k) => {78                  const url = codeUrl(k.system, k.code);79                  return (80                    <tr key={`${k.system}-${k.code}`}>81                      <td>{CODE_SYSTEM_LABEL[k.system] ?? k.system}</td>82                      <td>83                        {url ? (84                          <a className="ci-mono ci-link" href={url} target="_blank" rel="noopener noreferrer">85                            {k.code}86                          </a>87                        ) : (88                          <span className="ci-mono">{k.code}</span>89                        )}90                      </td>91                      <td>92                        <MatchBadge matchType={k.match_type} />93                      </td>94                      <td>95                        {k.source_slug ? (96                          <Link className="ci-link" href={`/source/${k.source_slug}`}>97                            {k.source_slug}98                          </Link>99                        ) : (100                          '—'101                        )}102                      </td>103                    </tr>104                  );105                })}106              </tbody>107            </table>108          </div>109        ) : (110          <EmptyState compact>No cross-reference code.</EmptyState>111        )}112      </Section>113114      <Section id="aliases" kicker="Names" title={`Aliases (${b.aliases.length})`}>115        {b.aliases.length ? (116          <div className="ci-table-wrap">117            <table className="ci-table">118              <thead>119                <tr>120                  <th>Alias</th>121                  <th>Type</th>122                  <th>Terminology</th>123                  <th>Language</th>124                </tr>125              </thead>126              <tbody>127                {b.aliases.map((a, i) => (128                  <tr key={`${a.alias}-${a.alias_type}-${i}`}>129                    <td>{a.alias}</td>130                    <td>131                      <Badge tone="outline">{a.alias_type}</Badge>132                    </td>133                    <td className="text-[12.5px] text-ink-3">{a.source_terminology ?? a.source_slug ?? '—'}</td>134                    <td className="ci-mono text-[12px]">en</td>135                  </tr>136                ))}137              </tbody>138            </table>139          </div>140        ) : (141          <EmptyState compact>No alias recorded.</EmptyState>142        )}143      </Section>144145      <Section id="history" kicker="Change history" title="Change events" description="Creation, updates, merges and ranking changes recorded for this entity.">146        {events.length ? (147          <ol className="divide-y divide-rule text-[13.5px]">148            {events.map((e) => (149              <li key={e.id} className="py-2">150                <div className="flex flex-wrap items-baseline gap-2">151                  <Badge>{humanize(e.kind)}</Badge>152                  <span>{e.summary}</span>153                  <span className="ml-auto text-[12px] text-ink-3">{fmtDate(e.created_at)}</span>154                </div>155                {e.ingest_run_id ? (156                  <Link href={`/admin/runs/${e.ingest_run_id}`} className="ci-mono ci-link text-[11.5px]">157                    {e.ingest_run_id}158                  </Link>159                ) : null}160                {e.before || e.after ? (161                  <details className="mt-1 text-[12px]">162                    <summary className="ci-link">diff</summary>163                    <div className="mt-1 grid gap-3 sm:grid-cols-2">164                      <div>165                        <p className="ci-kicker">Before</p>166                        <JsonView data={e.before} />167                      </div>168                      <div>169                        <p className="ci-kicker">After</p>170                        <JsonView data={e.after} />171                      </div>172                    </div>173                  </details>174                ) : null}175              </li>176            ))}177          </ol>178        ) : (179          <EmptyState compact>No change event recorded yet. Created {fmtDate(b.cancer.created_at)}; last updated {fmtDate(b.cancer.updated_at)}.</EmptyState>180        )}181      </Section>182    </div>183  );184}185