The ? opens upward, and covers the operations and Post
Opening downward put the popover under controls that already sit low in a scrolling dock, so the dock's edge cut it off. Up is the default now; `dir="down"` is for the few at the top of the panel, where up is the clipped direction. Scale, recode and clone each get one, and so does Post -- the three controls that actually change the version were the three with no explanation anywhere. They say what happens to the numbers and what is left alone: that scale writes the difference as new rows rather than editing what is there, that recode writes a negative and a positive under one entry, that clone leaves the original, and that Post is one change-log entry which undoes whole. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
e88f52203c
commit
6e09e90bdf
@ -97,7 +97,12 @@ const LABEL_W = 'w-24'
|
|||||||
// which put a line of prose beside every toggle -- and a line of prose beside a
|
// which put a line of prose beside every toggle -- and a line of prose beside a
|
||||||
// toggle is skipped, while still costing the dock a row. Hovering gives it
|
// toggle is skipped, while still costing the dock a row. Hovering gives it
|
||||||
// (native title, which nothing can clip), clicking pins it.
|
// (native title, which nothing can clip), clicking pins it.
|
||||||
function Help({ text }) {
|
//
|
||||||
|
// It opens upward, because the settings it explains sit low in a dock that
|
||||||
|
// scrolls -- a popover hanging below them is cut off by the dock's own edge.
|
||||||
|
// `dir="down"` is for the few that sit at the top of the panel, where up is the
|
||||||
|
// clipped direction instead.
|
||||||
|
function Help({ text, dir = 'up' }) {
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
if (!text) return null
|
if (!text) return null
|
||||||
return (
|
return (
|
||||||
@ -108,8 +113,9 @@ function Help({ text }) {
|
|||||||
open ? 'border-blue-400 text-blue-600 bg-blue-50' : 'border-gray-300 text-gray-500 hover:text-blue-600 hover:border-blue-400'}`}
|
open ? 'border-blue-400 text-blue-600 bg-blue-50' : 'border-gray-300 text-gray-500 hover:text-blue-600 hover:border-blue-400'}`}
|
||||||
style={{ fontSize: '10px' }}>?</button>
|
style={{ fontSize: '10px' }}>?</button>
|
||||||
{open && (
|
{open && (
|
||||||
<span className="absolute z-20 left-0 top-5 w-64 rounded border border-gray-200 bg-white shadow-lg px-2 py-1.5
|
<span className={`absolute z-20 left-0 w-64 rounded border border-gray-200 bg-white shadow-lg px-2 py-1.5
|
||||||
text-gray-600 leading-snug normal-case tracking-normal">
|
text-gray-600 leading-snug normal-case tracking-normal
|
||||||
|
${dir === 'down' ? 'top-5' : 'bottom-5'}`}>
|
||||||
{text}
|
{text}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@ -120,11 +126,11 @@ function Help({ text }) {
|
|||||||
// `stack` puts the label above its control instead of beside it. The aligned
|
// `stack` puts the label above its control instead of beside it. The aligned
|
||||||
// label column costs 6rem, which is most of a narrow zone -- the tag field in
|
// label column costs 6rem, which is most of a narrow zone -- the tag field in
|
||||||
// the bottom dock had 12rem of box left to type an initiative name into.
|
// the bottom dock had 12rem of box left to type an initiative name into.
|
||||||
function Field({ label, children, hint, stack }) {
|
function Field({ label, children, hint, hintDir, stack }) {
|
||||||
if (stack) {
|
if (stack) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-1 min-w-0">
|
<div className="flex flex-col gap-1 min-w-0">
|
||||||
<span className="text-gray-600 whitespace-nowrap flex items-center gap-1.5">{label}<Help text={hint} /></span>
|
<span className="text-gray-600 whitespace-nowrap flex items-center gap-1.5">{label}<Help text={hint} dir={hintDir} /></span>
|
||||||
<div className="flex items-center gap-2 min-w-0">{children}</div>
|
<div className="flex items-center gap-2 min-w-0">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -134,7 +140,7 @@ function Field({ label, children, hint, stack }) {
|
|||||||
<span className={`text-gray-600 whitespace-nowrap shrink-0 pt-1 ${LABEL_W}`}>{label}</span>
|
<span className={`text-gray-600 whitespace-nowrap shrink-0 pt-1 ${LABEL_W}`}>{label}</span>
|
||||||
<div className="flex items-center gap-2 flex-wrap min-w-0">
|
<div className="flex items-center gap-2 flex-wrap min-w-0">
|
||||||
{children}
|
{children}
|
||||||
<Help text={hint} />
|
<Help text={hint} dir={hintDir} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -1312,6 +1318,20 @@ function RequestPreview({ payload, op, versionId }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// What the three operations are, for the ? beside them. Written for somebody
|
||||||
|
// who has been handed the app, not for whoever wrote the SQL: what it does to
|
||||||
|
// the numbers, and what it leaves alone.
|
||||||
|
const OP_HELP = {
|
||||||
|
scale: 'Moves the selection to a number you name — a new total, a change, or a percentage. '
|
||||||
|
+ 'The rows already there are left alone: the difference is written as new rows underneath them, '
|
||||||
|
+ 'so the pivot re-sums and the whole thing is one entry you can undo.',
|
||||||
|
recode: 'Moves the selection to different dimension values — another customer, part or program. '
|
||||||
|
+ 'It writes the original back out as a negative and the same amounts in at the new coordinates, '
|
||||||
|
+ 'so nothing is lost and both halves undo together.',
|
||||||
|
clone: 'Copies the selection to new rows, optionally shifted in time and scaled. The original is untouched. '
|
||||||
|
+ 'This is how a period with no baseline of its own borrows its shape from prior year or plan.',
|
||||||
|
}
|
||||||
|
|
||||||
export default function OperationPanel({
|
export default function OperationPanel({
|
||||||
dock,
|
dock,
|
||||||
slices, setSlices, distinctSlices,
|
slices, setSlices, distinctSlices,
|
||||||
@ -1500,6 +1520,9 @@ export default function OperationPanel({
|
|||||||
<Submit onClick={() => submitOp(activeOp)} busy={!!opBusy}>
|
<Submit onClick={() => submitOp(activeOp)} busy={!!opBusy}>
|
||||||
{opBusy ? POSTING : POST}
|
{opBusy ? POSTING : POST}
|
||||||
</Submit>
|
</Submit>
|
||||||
|
<Help text={'Writes the adjustment onto the forecast, under your account, as one entry in the change log. '
|
||||||
|
+ 'The pivot updates in place — no reload. Nothing is overwritten: the entry can be undone whole '
|
||||||
|
+ 'from Change log, which removes exactly the rows it wrote.'} />
|
||||||
{opBusy && <span className="text-gray-500 leading-snug">writing rows…</span>}
|
{opBusy && <span className="text-gray-500 leading-snug">writing rows…</span>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -1517,7 +1540,7 @@ export default function OperationPanel({
|
|||||||
with no heading over it, so the panel never said what it was about
|
with no heading over it, so the panel never said what it was about
|
||||||
to do until you read the button. */}
|
to do until you read the button. */}
|
||||||
<div className="flex items-start gap-4 flex-wrap px-3 py-2 border-b border-gray-200 bg-gray-50/60">
|
<div className="flex items-start gap-4 flex-wrap px-3 py-2 border-b border-gray-200 bg-gray-50/60">
|
||||||
<Field label="Change">
|
<Field label="Change" hintDir="down" hint={OP_HELP[activeOp]}>
|
||||||
<div className="inline-flex rounded border border-gray-300 divide-x divide-gray-300 shadow-sm overflow-hidden">
|
<div className="inline-flex rounded border border-gray-300 divide-x divide-gray-300 shadow-sm overflow-hidden">
|
||||||
{['scale', 'recode', 'clone'].map(op => (
|
{['scale', 'recode', 'clone'].map(op => (
|
||||||
<Button key={op} onClick={() => setActiveOp(op)} active={activeOp === op}>
|
<Button key={op} onClick={() => setActiveOp(op)} active={activeOp === op}>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user