Drop the selection line; Post goes last
The summary printed back the cells you had just clicked in the grid -- count, total and filter, none of it news. It is a peek button now, and the peek still carries the pivot filter and the duplicate-slice warning, which are the parts that are not visible in the grid. Post moves to a footer of its own, last in the flow: pick the operation, read the ledger, name the change, post it. It sits on the same label column as every other button, and sticks to the bottom of the dock -- a fixed-height dock with its own scrollbar would otherwise put the primary action below the fold. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
20ed8e7484
commit
6af4902849
@ -166,45 +166,7 @@ function scopeText(viewScope = []) {
|
||||
.join(' · ')
|
||||
}
|
||||
|
||||
// The selection, in one line you can open. What it always shows is what is not
|
||||
// visible anywhere else: how many slices, what they total, and the pivot filter
|
||||
// they sit inside -- which scopes every figure in the ledger and every row the
|
||||
// write touches, while appearing in none of the slices themselves.
|
||||
function SliceSummary({ slices, viewScope, distinctSlices, currentTotals, open, onToggle }) {
|
||||
const scope = scopeText(viewScope)
|
||||
const one = slices.length === 1 ? sliceLabel(slices[0]) : null
|
||||
const total = currentTotals
|
||||
? (currentTotals.total?.value || 0) + (currentTotals.excluded?.value || 0)
|
||||
: null
|
||||
const dupes = distinctSlices != null && distinctSlices < slices.length
|
||||
|
||||
return (
|
||||
<div className="flex items-baseline gap-2 min-w-0">
|
||||
<span className="text-gray-600 shrink-0">
|
||||
{one ? '1 slice' : `${slices.length} slices`}
|
||||
</span>
|
||||
{one && <span className="font-mono text-gray-700 truncate max-w-[22rem]" title={one}>{one}</span>}
|
||||
{total != null && (
|
||||
<span className="font-mono tabular-nums text-gray-700 shrink-0">{fmtNum(total)}</span>
|
||||
)}
|
||||
{scope && (
|
||||
<span className="text-gray-500 truncate max-w-[16rem]" title={scope}>
|
||||
<span className="uppercase tracking-wide text-gray-400">within </span>
|
||||
<span className="font-mono">{scope}</span>
|
||||
</span>
|
||||
)}
|
||||
{dupes && (
|
||||
<span className="shrink-0 px-1 py-0.5 rounded bg-amber-50 text-amber-700 text-[10px] uppercase tracking-wide"
|
||||
title={`${slices.length} cells cover ${distinctSlices} distinct slices — some differ only by a column operations cannot target. The duplicates are applied once.`}>
|
||||
{distinctSlices} distinct
|
||||
</span>
|
||||
)}
|
||||
<TextButton onClick={onToggle}>{open ? '▾ hide' : '▸ peek'}</TextButton>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectionList({ slices, viewScope = [], currentTotals, onRemove, onClear }) {
|
||||
function SelectionList({ slices, viewScope = [], distinctSlices, currentTotals, onRemove, onClear }) {
|
||||
const multi = slices.length > 1
|
||||
const perSlice = currentTotals?.perSlice || []
|
||||
const valueCol = currentTotals?.valueCol
|
||||
@ -226,6 +188,15 @@ function SelectionList({ slices, viewScope = [], currentTotals, onRemove, onClea
|
||||
|
||||
return (
|
||||
<div className="min-w-0">
|
||||
{/* Cells that differ only by a column operations cannot filter on collapse
|
||||
to the same slice — say so, rather than implying more reach than there is */}
|
||||
{distinctSlices != null && distinctSlices < slices.length && (
|
||||
<p className="text-amber-700 bg-amber-50 border border-amber-200 rounded px-2 py-1 mb-2 leading-snug">
|
||||
{slices.length} cells cover {distinctSlices} distinct{' '}
|
||||
{distinctSlices === 1 ? 'slice' : 'slices'} — some differ only by a column
|
||||
operations cannot target. The duplicates are applied once.
|
||||
</p>
|
||||
)}
|
||||
{scopeLine && (
|
||||
<div className="mb-1 flex items-baseline gap-1.5 text-[11px]">
|
||||
<span className="text-gray-400 uppercase tracking-wide shrink-0">within</span>
|
||||
@ -1450,13 +1421,10 @@ export default function OperationPanel({
|
||||
</Field>
|
||||
|
||||
{hasSlice && (
|
||||
<div className="ml-auto flex items-center gap-3 pt-0.5">
|
||||
<SliceSummary slices={slices} viewScope={viewScope} distinctSlices={distinctSlices}
|
||||
currentTotals={currentTotals} open={peek} onToggle={() => setPeek(o => !o)} />
|
||||
{opBusy && <span className="text-gray-500">writing rows…</span>}
|
||||
<Submit onClick={() => submitOp(activeOp)} busy={!!opBusy}>
|
||||
{opBusy ? POSTING : POST}
|
||||
</Submit>
|
||||
<div className="ml-auto pt-1">
|
||||
<TextButton onClick={() => setPeek(o => !o)}>
|
||||
{peek ? '▾' : '▸'} {slices.length} {slices.length === 1 ? 'slice' : 'slices'}
|
||||
</TextButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -1476,6 +1444,7 @@ export default function OperationPanel({
|
||||
<SelectionList
|
||||
slices={slices}
|
||||
viewScope={viewScope}
|
||||
distinctSlices={distinctSlices}
|
||||
currentTotals={currentTotals}
|
||||
onRemove={(i) => setSlices(prev => prev.filter((_, x) => x !== i))}
|
||||
onClear={() => setSlices([])}
|
||||
@ -1506,6 +1475,23 @@ export default function OperationPanel({
|
||||
<Block title="Label this change" horizontal={horizontal}>{labelZone}</Block>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Last, because it is last: pick the operation, read the ledger, name
|
||||
the change, post it. The blank label keeps Post in the same column as
|
||||
every other button in the panel rather than at an edge of its own.
|
||||
Sticky, because the bottom dock is a fixed height with its own scroll
|
||||
-- a primary action that has to be scrolled to is a primary action
|
||||
people miss. */}
|
||||
{hasSlice && (
|
||||
<div className="sticky bottom-0 z-10 flex items-center gap-3 px-3 py-2 border-t border-gray-200 bg-gray-50">
|
||||
<Field label="">
|
||||
<Submit onClick={() => submitOp(activeOp)} busy={!!opBusy}>
|
||||
{opBusy ? POSTING : POST}
|
||||
</Submit>
|
||||
</Field>
|
||||
{opBusy && <span className="text-gray-500">writing rows — the pivot updates when it finishes</span>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user