SPB Git

Repository Public

SheetView.tsx 2.6 KB 40 lines tsx

1// Workbook preview: the worker read the values of each sheet (never formulas' code, never macros) into JSON.2import { useMemo, useState } from 'react';3import { useT } from '../i18n.ts';4import type { PreviewSource } from '../types.ts';5import { cx } from '../ui.tsx';6import { columnLetter, Grid } from './Grid.tsx';7import { DerivedGate, Note, useDerived } from './shared.tsx';89/** Same shape as SheetPreview in services/api/src/tools-sheet.ts. */10export interface SheetPreview { sheets: { name: string; rows: string[][]; totalRows: number; totalColumns: number; truncated: boolean }[]; truncatedSheets: boolean }11const LIMIT = { rows: 1000, columns: 60, sheets: 30 };1213function Workbook({ data }: { data: SheetPreview }) {14  const t = useT();15  const [index, setIndex] = useState(0);16  const sheet = data.sheets[Math.min(index, data.sheets.length - 1)];17  const columns = useMemo(() => (sheet ? sheet.rows.reduce((m, r) => Math.max(m, r.length), 0) : 0), [sheet]);18  const header = useMemo(() => Array.from({ length: columns }, (_, i) => columnLetter(i)), [columns]);19  if (!sheet) return <Note testId="empty-note">{t('preview.sheetNone')}</Note>;20  return (21    <>22      {sheet.truncated ? <Note tone="warn" testId="sheet-truncated">{t('preview.sheetTruncated', { rows: LIMIT.rows, cols: LIMIT.columns, totalRows: sheet.totalRows, totalCols: sheet.totalColumns })}</Note> : null}23      {data.truncatedSheets ? <Note tone="warn" testId="sheets-truncated">{t('preview.sheetsTruncated', { n: LIMIT.sheets })}</Note> : null}24      {sheet.rows.length === 0 || columns === 025        ? <div className="flex min-h-0 flex-1 items-center justify-center bg-surface p-6 text-sm text-ink-3" data-testid="sheet-empty">{t('preview.sheetEmpty')}</div>26        : <Grid header={header} rows={sheet.rows} columns={columns} label={sheet.name} />}27      <div role="tablist" aria-label={t('preview.sheets')} className="flex shrink-0 items-stretch gap-1 overflow-x-auto border-t border-line bg-surface px-2 py-1" data-noswipe>28        {data.sheets.map((s, i) => (29          <button key={i} type="button" role="tab" aria-selected={i === index} onClick={() => setIndex(i)} data-testid="sheet-tab"30            className={cx('h-10 max-w-48 shrink-0 truncate rounded-xl px-3 text-sm font-medium transition', i === index ? 'bg-accent-soft text-accent' : 'text-ink-2 hover:bg-hover hover:text-ink')}>{s.name}</button>31        ))}32      </div>33    </>34  );35}36export function SheetView({ source }: { source: PreviewSource }) {37  const [state, retry] = useDerived<SheetPreview>(source.previewUrl('sheet'), 'json');38  return <DerivedGate state={state} retry={retry} source={source}>{(data) => <Workbook data={data} />}</DerivedGate>;39}40
Files main
Menu

Theme