import { type ReactNode } from 'react'; import { editableValue, isComputed, type ColumnMeta, type Row } from '@wellformed/shared'; import { useRows } from '../api/hooks'; import { useI18n } from '../i18n'; import { useWorkspace } from '../state/workspace'; import { useCurrency } from '../state/session'; import { typeMark } from './Icons'; /** * The address-and-value strip above the grid. It reads; it does not write. * * It used to offer a text field for every column, which is a promise this * product cannot keep: a select, a date, a checkbox and a link each have a * shape, and a bare text box can only send something the server then has to * refuse — after a round trip, with the typing thrown away. Matching the cell * editor's controls here would mean building that editor a second time, so * writing stays in the cell, where every type already has the right control. * * What is left is what only this strip can say: which cell you are in, the * whole of a value too long for its column, and — for a computed column — the * expression the database is actually evaluating. */ export function FormulaBar(): ReactNode { const ws = useWorkspace(); const currency = useCurrency(); const { t } = useI18n(); const { rows } = useRows({ table: ws.tableName, viewId: ws.viewId, filters: ws.adHocFilters, sort: ws.sortOverride, hidden: ws.hiddenOverride, search: ws.search, enabled: Boolean(ws.tableName), }); const selection = ws.selection; const column = ws.table?.columns.find((c) => c.name === selection?.col); const row = rows.find((r) => r.id === selection?.rowId); const computed = column ? isComputed(column.type) : false; // A link's stored value is a row id. Showing it here would contradict the // grid, which names the row, so this strip names it too. const isLink = column?.type === 'link'; const stored = column && row ? editableValue(row.values[column.name] ?? null) : ''; const shown = computed && column ? `= ${column.expr ?? ''}` : isLink ? linkLabel(row, column) : stored; const rowLabel = describeRow(row, ws.table?.display); const address = selection ? `${ws.tableName}!${selection.col} · ${rowLabel}` : t('grid.noSelection'); // With nothing selected the strip has nothing to address, but it keeps its // row: collapsing it moved the grid up under the pointer, so the click that // selected the first cell dropped the header onto the row you were aiming // at — and a double-click to edit sorted the table instead. return (
{address} {/* Excel puts `fx` here because an Excel cell can hold a formula. A Wellformed cell never can: the expression belongs to the column, and this strip is showing a value. So the marker says which kind of column you are in — the same vocabulary as the grid header — and reads `fx` only where the field really is holding an expression. */} {column ? typeMark(column.type, currency) : ''} {/* Selectable, so a value can still be copied out of here, and scrollable rather than clipped, so a long one can be read in full. */} {shown} {computed && column && ( {t('grid.evaluatedInSql', column.type)} )} {isLink && column && ( {t('grid.foreignKey', column.target ?? t('grid.anotherTable'))} )}
); } /** The name of the row a link points at, falling back to the id it stores. */ function linkLabel(row: Row | undefined, column: ColumnMeta): string { if (!row) return ''; const value = row.values[column.name]; if (value === null || value === undefined || value === '') return ''; return row.labels[column.name] ?? String(value); } function describeRow(row: Row | undefined, displayColumn: string | undefined): string { if (!row) return ''; if (!displayColumn) return row.id; const value = row.values[displayColumn]; return value === null || value === undefined || value === '' ? row.id : String(value); }