Say who made each change, and which ones you can undo
The change log showed what happened and never who did it, which stops being a detail the moment more than one person is in the version. The Undo button greys out on entries belonging to someone else, with the reason on hover. The server already refused them; the button offered the click anyway and answered with a 403, which reads as a fault rather than a rule. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9d08638a92
commit
61268f2a7a
@ -1,5 +1,6 @@
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import useTheme from '../theme.jsx'
|
||||
import useAuth from '../auth.jsx'
|
||||
import OperationPanel from '../components/OperationPanel.jsx'
|
||||
import BridgeView from '../components/BridgeView.jsx'
|
||||
|
||||
@ -54,6 +55,11 @@ const DEAD_ORDER_EXPRS = ['pf_bucket_ord', 'pf_segment_ord', 'Bucket', 'Segment'
|
||||
|
||||
export default function Forecast({ sources = [], sourceId, versions = [], versionId, refreshSources }) {
|
||||
const { dark } = useTheme()
|
||||
const { user } = useAuth()
|
||||
// Undo removes an entry's rows wholesale, so the server allows it only to the
|
||||
// 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)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [largeDataset, setLargeDataset] = useState(false)
|
||||
// The pivot's own filter, refreshed whenever the ledger recomputes. A ref
|
||||
@ -1902,6 +1908,7 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
|
||||
<th className="text-left px-3 py-2 font-medium">Slice</th>
|
||||
<th className="text-left px-3 py-2 font-medium w-32">Tag</th>
|
||||
<th className="text-left px-3 py-2 font-medium w-48">Note</th>
|
||||
<th className="text-left px-3 py-2 font-medium w-24">By</th>
|
||||
<th className="text-right px-3 py-2 font-medium w-28">Value</th>
|
||||
<th className="text-right px-3 py-2 font-medium w-16">Rows</th>
|
||||
<th className="px-3 py-2 w-16"></th>
|
||||
@ -1935,6 +1942,9 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
|
||||
)} />
|
||||
<LogCell entry={entry} field="note" placeholder="add note"
|
||||
editing={editingCell} setEditing={setEditingCell} onSave={saveLogField} />
|
||||
<td className="px-3 py-2 text-gray-500 truncate" title={entry.pf_user || ''}>
|
||||
{entry.pf_user || '—'}
|
||||
</td>
|
||||
<td className={`px-3 py-2 text-right tabular-nums whitespace-nowrap ${
|
||||
entry.value_total > 0 ? 'text-green-700' : entry.value_total < 0 ? 'text-red-600' : 'text-gray-400'}`}>
|
||||
{entry.value_total == null ? '—'
|
||||
@ -1944,8 +1954,15 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
|
||||
<td className="px-3 py-2">
|
||||
<button
|
||||
onClick={() => undoEntry(entry.id)}
|
||||
disabled={undoingId === entry.id}
|
||||
className="text-xs border border-red-200 text-red-400 hover:text-red-600 hover:border-red-400 rounded px-2 py-0.5 disabled:opacity-40 whitespace-nowrap">
|
||||
disabled={undoingId === entry.id || !canUndo(entry)}
|
||||
title={canUndo(entry)
|
||||
? 'Remove this entry and its rows'
|
||||
: `Only ${entry.pf_user || 'the author'} or an administrator can undo this`}
|
||||
className={`text-xs rounded px-2 py-0.5 whitespace-nowrap border ${
|
||||
canUndo(entry)
|
||||
? 'border-red-200 text-red-400 hover:text-red-600 hover:border-red-400'
|
||||
: 'border-gray-100 text-gray-300 cursor-not-allowed'
|
||||
} disabled:opacity-100`}>
|
||||
{undoingId === entry.id ? '…' : 'Undo'}
|
||||
</button>
|
||||
</td>
|
||||
@ -1955,7 +1972,7 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
|
||||
if (expandedLog !== entry.id) return [row]
|
||||
return [row, (
|
||||
<tr key={`${entry.id}-detail`} className="bg-gray-50 border-t border-gray-100">
|
||||
<td colSpan={8} className="px-3 py-3">
|
||||
<td colSpan={9} className="px-3 py-3">
|
||||
<div className="grid gap-3 md:grid-cols-2">
|
||||
<LogJson label="slice" value={entry.slice} />
|
||||
<LogJson label="params" value={entry.params} />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user