Restore functional changes lost in dark theme revert

Re-applies fetchCurrentTotals dimension-only filter (prevents Perspective errors
from split_by/expression columns in the filter), toolbar three-group reorganization
(Layout | Expand | Data with dividers), always-visible Save as…, msg in toolbar,
resizable panel, change log modal with undo and inline note editing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Paul Trowbridge 2026-04-25 21:32:00 -04:00
parent 8f009e468e
commit bd5ea1c60e

View File

@ -119,8 +119,11 @@ export default function Forecast() {
const unitsCol = colMetaRef.current.find(c => c.role === 'units')?.cname const unitsCol = colMetaRef.current.find(c => c.role === 'units')?.cname
if (!valueCol && !unitsCol) return if (!valueCol && !unitsCol) return
try { try {
const dimNames = new Set(colMetaRef.current.filter(c => c.role === 'dimension').map(c => c.cname))
const filters = [ const filters = [
...Object.entries(sliceObj).map(([col, val]) => [col, '==', val]), ...Object.entries(sliceObj)
.filter(([col]) => dimNames.has(col))
.map(([col, val]) => [col, '==', val]),
['pf_iter', '!=', 'reference'], ['pf_iter', '!=', 'reference'],
] ]
const view = await tableRef.current.view({ filter: filters }) const view = await tableRef.current.view({ filter: filters })
@ -402,68 +405,79 @@ export default function Forecast() {
</span> </span>
)} )}
</div> </div>
{msg && (
<span className={`text-xs font-medium px-2 py-0.5 rounded ml-auto ${msg.type === 'error' ? 'bg-red-50 text-red-600' : 'bg-green-50 text-green-600'}`}>
{msg.text}
</span>
)}
</div> </div>
{/* Layout / depth bar */} {/* Toolbar */}
<div className="px-3 py-1.5 border-b border-gray-200 bg-white flex items-center gap-2 shrink-0 flex-wrap"> <div className="px-3 py-1.5 border-b border-gray-200 bg-white flex items-center gap-3 shrink-0 flex-wrap text-xs">
<span className="text-xs text-gray-400 uppercase tracking-wide">Layouts</span>
{/* Layout group */}
<div className="flex items-center gap-1.5">
<span className="text-gray-400 uppercase tracking-wide" style={{fontSize:'10px'}}>Layout</span>
{layouts.map(l => ( {layouts.map(l => (
<div key={l.id} onClick={() => applyLayout(l)} <div key={l.id} onClick={() => applyLayout(l)}
className={`flex items-center gap-1 text-xs rounded px-2 py-0.5 cursor-pointer border transition-colors className={`flex items-center gap-1 rounded px-2 py-0.5 cursor-pointer border transition-colors
${activeLayoutId === l.id ? 'bg-blue-50 border-blue-300 text-blue-700' : 'bg-white border-gray-200 text-gray-600 hover:border-gray-400'}`}> ${activeLayoutId === l.id ? 'bg-blue-50 border-blue-300 text-blue-700' : 'bg-white border-gray-200 text-gray-600 hover:border-gray-400'}`}>
{l.name} {l.name}
<button onClick={e => deleteLayout(l.id, e)} className="text-gray-300 hover:text-red-400 text-sm leading-none ml-0.5">×</button> <button onClick={e => deleteLayout(l.id, e)} className="text-gray-300 hover:text-red-400 text-sm leading-none ml-0.5">×</button>
</div> </div>
))} ))}
{activeLayoutId !== null && !showSaveAs && (
<button onClick={handleSaveOver} className="text-xs border border-blue-200 text-blue-500 hover:text-blue-700 rounded px-2 py-0.5">Save</button>
)}
{showSaveAs ? ( {showSaveAs ? (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<input autoFocus value={saveAsName} onChange={e => setSaveAsName(e.target.value)} <input autoFocus value={saveAsName} onChange={e => setSaveAsName(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') handleSaveAs(); if (e.key === 'Escape') { setShowSaveAs(false); setSaveAsName('') } }} onKeyDown={e => { if (e.key === 'Enter') handleSaveAs(); if (e.key === 'Escape') { setShowSaveAs(false); setSaveAsName('') } }}
placeholder="Layout name…" className="text-xs border border-gray-300 rounded px-2 py-0.5 w-32 focus:outline-none focus:border-blue-400" /> placeholder="Layout name…" className="border border-gray-300 rounded px-2 py-0.5 w-32 focus:outline-none focus:border-blue-400" />
<button onClick={handleSaveAs} className="text-xs text-blue-600 hover:text-blue-800 px-1">Save</button> <button onClick={handleSaveAs} className="text-blue-600 hover:text-blue-800 px-1">Save</button>
<button onClick={() => { setShowSaveAs(false); setSaveAsName('') }} className="text-xs text-gray-400 px-1">Cancel</button> <button onClick={() => { setShowSaveAs(false); setSaveAsName('') }} className="text-gray-400 px-1">Cancel</button>
</div> </div>
) : ( ) : (
<button onClick={() => setShowSaveAs(true)} className="text-xs border border-dashed border-gray-200 text-gray-400 hover:text-gray-600 rounded px-2 py-0.5"> <>
+ Save as
</button>
)}
{activeLayoutId !== null && ( {activeLayoutId !== null && (
<button onClick={resetLayout} className="text-xs text-gray-300 hover:text-gray-500">reset</button> <button onClick={handleSaveOver} className="border border-blue-200 text-blue-500 hover:text-blue-700 rounded px-2 py-0.5">Save</button>
)} )}
<button onClick={() => setShowSaveAs(true)} className="border border-dashed border-gray-200 text-gray-400 hover:text-gray-600 rounded px-2 py-0.5">
<button onClick={openLog} disabled={!versionId} Save as
className="text-xs border border-gray-200 rounded px-2 py-0.5 text-gray-500 hover:bg-gray-50 disabled:opacity-40">
History
</button> </button>
{activeLayoutId !== null && (
<button onClick={resetLayout} className="text-gray-300 hover:text-red-400">Reset</button>
)}
</>
)}
</div>
{/* Depth controls */} <div className="w-px h-4 bg-gray-200 shrink-0" />
<div className="ml-auto flex items-center gap-1.5">
<span className="text-xs text-gray-400">depth</span> {/* Expand group */}
<div className="flex items-center gap-1.5">
<span className="text-gray-400 uppercase tracking-wide" style={{fontSize:'10px'}}>Expand</span>
{[0, 1, 2, 3].map(d => ( {[0, 1, 2, 3].map(d => (
<button key={d} onClick={() => applyDepth(d)} <button key={d} onClick={() => applyDepth(d)}
className={`text-xs border rounded px-1.5 py-0.5 transition-colors className={`border rounded px-1.5 py-0.5 transition-colors
${expandDepthRef.current === d ? 'border-blue-300 text-blue-600 bg-blue-50' : 'border-gray-200 text-gray-500 hover:border-gray-400'}`}> ${expandDepthRef.current === d ? 'border-blue-300 text-blue-600 bg-blue-50' : 'border-gray-200 text-gray-500 hover:border-gray-400'}`}>
{d} {d}
</button> </button>
))} ))}
</div>
<div className="w-px h-4 bg-gray-200 shrink-0" />
{/* Data group */}
<div className="flex items-center gap-1.5">
<button onClick={() => initViewer(versionId, sourceId)} disabled={loading || !versionId} <button onClick={() => initViewer(versionId, sourceId)} disabled={loading || !versionId}
className="text-xs border border-gray-200 rounded px-2 py-0.5 text-gray-500 hover:bg-gray-50 disabled:opacity-40 ml-2"> className="border border-gray-200 rounded px-2 py-0.5 text-gray-500 hover:bg-gray-50 disabled:opacity-40">
{loading ? 'Loading…' : 'Refresh'} {loading ? 'Loading…' : 'Refresh data'}
</button>
<button onClick={openLog} disabled={!versionId}
className="border border-gray-200 rounded px-2 py-0.5 text-gray-500 hover:bg-gray-50 disabled:opacity-40">
Change log
</button> </button>
</div> </div>
{msg && (
<span className={`ml-2 text-xs font-medium px-2 py-0.5 rounded ${msg.type === 'error' ? 'bg-red-50 text-red-600' : 'bg-green-50 text-green-600'}`}>
{msg.text}
</span>
)}
</div> </div>
{/* History modal */} {/* History modal */}