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:
parent
8f009e468e
commit
bd5ea1c60e
@ -119,8 +119,11 @@ export default function Forecast() {
|
||||
const unitsCol = colMetaRef.current.find(c => c.role === 'units')?.cname
|
||||
if (!valueCol && !unitsCol) return
|
||||
try {
|
||||
const dimNames = new Set(colMetaRef.current.filter(c => c.role === 'dimension').map(c => c.cname))
|
||||
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'],
|
||||
]
|
||||
const view = await tableRef.current.view({ filter: filters })
|
||||
@ -402,68 +405,79 @@ export default function Forecast() {
|
||||
</span>
|
||||
)}
|
||||
</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>
|
||||
|
||||
{/* Layout / depth bar */}
|
||||
<div className="px-3 py-1.5 border-b border-gray-200 bg-white flex items-center gap-2 shrink-0 flex-wrap">
|
||||
<span className="text-xs text-gray-400 uppercase tracking-wide">Layouts</span>
|
||||
{/* Toolbar */}
|
||||
<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">
|
||||
|
||||
{layouts.map(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
|
||||
${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}
|
||||
<button onClick={e => deleteLayout(l.id, e)} className="text-gray-300 hover:text-red-400 text-sm leading-none ml-0.5">×</button>
|
||||
</div>
|
||||
))}
|
||||
{/* 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 => (
|
||||
<div key={l.id} onClick={() => applyLayout(l)}
|
||||
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'}`}>
|
||||
{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>
|
||||
</div>
|
||||
))}
|
||||
{showSaveAs ? (
|
||||
<div className="flex items-center gap-1">
|
||||
<input autoFocus value={saveAsName} onChange={e => setSaveAsName(e.target.value)}
|
||||
onKeyDown={e => { if (e.key === 'Enter') handleSaveAs(); if (e.key === 'Escape') { setShowSaveAs(false); setSaveAsName('') } }}
|
||||
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-blue-600 hover:text-blue-800 px-1">Save</button>
|
||||
<button onClick={() => { setShowSaveAs(false); setSaveAsName('') }} className="text-gray-400 px-1">Cancel</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{activeLayoutId !== null && (
|
||||
<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">
|
||||
Save as…
|
||||
</button>
|
||||
{activeLayoutId !== null && (
|
||||
<button onClick={resetLayout} className="text-gray-300 hover:text-red-400">Reset</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</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>
|
||||
)}
|
||||
<div className="w-px h-4 bg-gray-200 shrink-0" />
|
||||
|
||||
{showSaveAs ? (
|
||||
<div className="flex items-center gap-1">
|
||||
<input autoFocus value={saveAsName} onChange={e => setSaveAsName(e.target.value)}
|
||||
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" />
|
||||
<button onClick={handleSaveAs} className="text-xs 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>
|
||||
</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 && (
|
||||
<button onClick={resetLayout} className="text-xs text-gray-300 hover:text-gray-500">reset</button>
|
||||
)}
|
||||
|
||||
<button onClick={openLog} disabled={!versionId}
|
||||
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>
|
||||
|
||||
{/* Depth controls */}
|
||||
<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 => (
|
||||
<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'}`}>
|
||||
{d}
|
||||
</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}
|
||||
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">
|
||||
{loading ? 'Loading…' : 'Refresh'}
|
||||
className="border border-gray-200 rounded px-2 py-0.5 text-gray-500 hover:bg-gray-50 disabled:opacity-40">
|
||||
{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>
|
||||
</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>
|
||||
|
||||
{/* History modal */}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user