Spreadsheet-UX op een echte relationele engine
Deze repository wordt als momentopname gepubliceerd: een clone bevat de volledige inhoud, maar geen commitgeschiedenis.
import { useState, type ReactNode } from 'react';
import { useRows, useSummary } from '../api/hooks';
import { useI18n } from '../i18n';
import { useWorkspace } from '../state/workspace';
import { useToasts } from '../state/toasts';
/**
* The signature: the statement the server actually ran for the current view,
* with literals inlined. Not an illustration — this is the query, echoed back
* from the response that produced the rows above it.
*
* Collapsed by default. Shown once on request it is a proof that the grid is a
* real query; shown always it was a truncated ribbon nobody could read.
*/
export function SqlBar(): ReactNode {
const ws = useWorkspace();
const toasts = useToasts();
const { t, locale } = useI18n();
const [open, setOpen] = useState(false);
const { sql, tokens, total } = useRows({
table: ws.tableName,
viewId: ws.viewId,
filters: ws.adHocFilters,
sort: ws.sortOverride,
hidden: ws.hiddenOverride,
search: ws.search,
enabled: Boolean(ws.tableName),
});
// The aggregate row is a second statement, so it gets a second line. Hiding
// it would make the footer the one number on this screen with no working out
// shown — which is the opposite of what this bar is for.
const summary = useSummary({
table: ws.tableName,
viewId: ws.viewId,
filters: ws.adHocFilters,
hidden: ws.hiddenOverride,
search: ws.search,
summaries: ws.summaries,
enabled: Boolean(ws.tableName),
});
if (!sql) return null;
return (
<div className={'sqlbar' + (open ? ' open' : '')}>
{/* The whole line toggles — a thin dark strip whose only clickable part
is one small label invites misses. The buttons stop propagation so
they keep their own meaning. */}
<div className="sqlbar-line" onClick={() => setOpen(!open)}>
<span className="lbl">{t('grid.sqlRowCount', total, locale)}</span>
<button
className="disclose"
onClick={(e) => {
e.stopPropagation();
setOpen(!open);
}}
aria-expanded={open}
>
{open ? t('grid.hideSql') : t('grid.viewSql')}
</button>
{open && (
<button
className="copy"
onClick={(e) => {
e.stopPropagation();
navigator.clipboard
.writeText(sql)
.then(() => toasts.push(t('grid.sqlCopied'), 'success'))
.catch(() => toasts.push(t('grid.clipboardRefused'), 'error'));
}}
>
{t('grid.copySql')}
</button>
)}
</div>
{open && (
// One scroll container around both statements: two panes that scroll
// apart would make the pair read as two bars instead of one proof.
<div className="stmts">
<code className="stmt" aria-label={t('grid.sqlForView')}>
{tokens.map((token, i) => (
<span key={i} className={token.c}>
{token.s}
</span>
))}
</code>
{summary.sql && (
<code className="stmt" aria-label={t('grid.sqlForSummary')}>
{summary.tokens.map((token, i) => (
<span key={i} className={token.c}>
{token.s}
</span>
))}
</code>
)}
</div>
)}
</div>
);
}