diff --git a/CLAUDE.md b/CLAUDE.md index f8f1f0f..46beb80 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -755,6 +755,22 @@ Enforced at: Moving a row between territories is reassignment, not forecasting, and it would vanish from the view that would have shown what happened. +**Whose entries the change log lists.** Everyone's, as before, with an +**Everyone / Mine** control in the modal header and each account's own entries +marked in place — a blue rule down the left of the row and its `By` cell in +blue. On the version this was built against that is 127 adjustments from five +accounts, one of them the reader's, so finding your own meant reading the `By` +column down the whole list. + +Marked *and* filterable on purpose: the mark is how you tell without hiding +anybody, the filter is for when you want only yours. It resets to Everyone on +every open and is not persisted — a filter that hides other people's work is +fine as a thing you asked for and a trap as a thing you inherited from last +week. `isMine()` is strict authorship, deliberately not `canUndo()`: an admin +can undo everything, and marking every row as theirs would say nothing. + +The fetch stays unfiltered, since the counts on the control need the whole set. + **The change log shows an entry's full impact**, not the reader's share. The totals stamped on `pf.log` are company-wide, so an admin's version-wide scale reads the same in every account — deliberate, and labelled, rather than diff --git a/ui/src/views/Forecast.jsx b/ui/src/views/Forecast.jsx index eb96dc5..02298b3 100644 --- a/ui/src/views/Forecast.jsx +++ b/ui/src/views/Forecast.jsx @@ -76,6 +76,9 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio // account that made it, or an admin. Mirrored here to say so before the click // rather than after the 403. const canUndo = (entry) => !!user && (user.is_admin || entry.pf_user === user.username) + // Strictly authorship, which is not the same test as canUndo: an admin can + // undo everything, and marking every row as theirs would say nothing. + const isMine = (entry) => !!user && entry.pf_user === user.username const [loading, setLoading] = useState(false) const [largeDataset, setLargeDataset] = useState(false) // The pivot's own filter, refreshed whenever the ledger recomputes. A ref @@ -252,6 +255,10 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio const [showBridge, setShowBridge] = useState(false) const [logEntries, setLogEntries] = useState([]) const [logLoading, setLogLoading] = useState(false) + // Whose entries the log lists. Not persisted, and it resets to everyone on + // every open: a filter that hides other people's work is fine as a thing you + // asked for and a trap as a thing you inherited from last week. + const [logWho, setLogWho] = useState('all') // 'all' | 'mine' const [editingCell, setEditingCell] = useState(null) // { id, field: 'note'|'tag', text } const [undoingId, setUndoingId] = useState(null) @@ -1725,6 +1732,10 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio return out } + // What the history modal lists. The fetch is unfiltered -- the counts on the + // Everyone/Mine control need the whole set -- so the narrowing happens here. + const visibleLog = logWho === 'mine' ? logEntries.filter(isMine) : logEntries + // The scope is read from the ref rather than passed in: the request preview in // the panel calls this too, and when it was a parameter the preview defaulted // it away -- showing a payload with no scope for a write that had one. @@ -1817,6 +1828,7 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio async function openLog() { setShowLog(true) setExpandedLog(null) + setLogWho('all') setLogLoading(true) try { // Baseline and reference loads are segment construction, not forecasting. @@ -2043,15 +2055,38 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
setShowLog(false)}>
e.stopPropagation()}>
- Change History +
+ Change History + {/* Whose work this is. A version under active use carries + everyone's adjustments, and finding your own in it meant + reading the By column down a list that is mostly other + people. Own entries are marked in place as well, so the + filter is a way to narrow rather than the only way to tell. */} + {!!user && logEntries.length > 0 && ( +
+ {[['all', `Everyone`, logEntries.length], + ['mine', `Mine`, logEntries.filter(isMine).length]].map(([val, label, n]) => ( + + ))} +
+ )} +
{logLoading ? (
Loading…
- ) : logEntries.length === 0 ? ( -
No log entries yet.
+ ) : visibleLog.length === 0 ? ( +
+ {logEntries.length === 0 + ? 'No log entries yet.' + : `None of the ${logEntries.length} entries here are yours.`} +
) : ( @@ -2068,9 +2103,12 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio - {logEntries.map(entry => ( + {visibleLog.map(entry => ( - + )).flatMap((row, i) => { - const entry = logEntries[i] + const entry = visibleLog[i] if (expandedLog !== entry.id) return [row] return [row, (
{fmtStamp(entry.stamp)} + {fmtStamp(entry.stamp)} + {entry.operation} @@ -2095,7 +2133,8 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio )} /> - + {entry.pf_user || '—'}