App.jsx owns sources/sourceId/versions/versionId and persists them across reloads. StatusBar renders the dropdowns plus a status badge — Source-only on Setup, Source · Version · status on Baseline/Forecast. The duplicate in-view selector bars in Forecast and Baseline are gone; Baseline keeps its version actions (New/Close/Reopen/Delete) inline. Setup reports source-list mutations up via refreshSources so the bar stays in sync after register/deregister. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
75 lines
2.8 KiB
JavaScript
75 lines
2.8 KiB
JavaScript
import { useState, useEffect, useCallback } from 'react'
|
|
import Sidebar from './components/Sidebar.jsx'
|
|
import StatusBar from './components/StatusBar.jsx'
|
|
import Setup from './views/Setup.jsx'
|
|
import Baseline from './views/Baseline.jsx'
|
|
import Forecast from './views/Forecast.jsx'
|
|
|
|
export default function App() {
|
|
const [view, setView] = useState(() => localStorage.getItem('pf_view') || 'forecast')
|
|
const [sidebarExpanded, setSidebarExpanded] = useState(() => localStorage.getItem('pf_sidebar') !== 'collapsed')
|
|
|
|
const [sources, setSources] = useState([])
|
|
const [sourceId, setSourceId] = useState(() => localStorage.getItem('pf_sourceId') || '')
|
|
const [versions, setVersions] = useState([])
|
|
const [versionId, setVersionId] = useState(() => localStorage.getItem('pf_versionId') || '')
|
|
|
|
useEffect(() => { localStorage.setItem('pf_view', view) }, [view])
|
|
useEffect(() => { localStorage.setItem('pf_sidebar', sidebarExpanded ? 'expanded' : 'collapsed') }, [sidebarExpanded])
|
|
useEffect(() => { localStorage.setItem('pf_sourceId', sourceId || '') }, [sourceId])
|
|
useEffect(() => { localStorage.setItem('pf_versionId', versionId || '') }, [versionId])
|
|
|
|
const refreshSources = useCallback(async () => {
|
|
const data = await fetch('/api/sources').then(r => r.json())
|
|
setSources(data)
|
|
return data
|
|
}, [])
|
|
|
|
const refreshVersions = useCallback(async (sid) => {
|
|
const id = sid ?? sourceId
|
|
if (!id) { setVersions([]); return [] }
|
|
const data = await fetch(`/api/sources/${id}/versions`).then(r => r.json())
|
|
setVersions(data)
|
|
return data
|
|
}, [sourceId])
|
|
|
|
useEffect(() => {
|
|
refreshSources().then(data => {
|
|
if (data.length === 0) { setSourceId(''); return }
|
|
if (!sourceId || !data.some(s => String(s.id) === String(sourceId))) {
|
|
setSourceId(String(data[0].id))
|
|
}
|
|
})
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
if (!sourceId) { setVersions([]); setVersionId(''); return }
|
|
refreshVersions(sourceId).then(data => {
|
|
if (data.length === 0) { setVersionId(''); return }
|
|
if (!versionId || !data.some(v => String(v.id) === String(versionId))) {
|
|
setVersionId(String(data[0].id))
|
|
}
|
|
})
|
|
}, [sourceId])
|
|
|
|
const ctx = {
|
|
sources, sourceId, setSourceId,
|
|
versions, versionId, setVersionId,
|
|
refreshSources, refreshVersions, setVersions,
|
|
}
|
|
|
|
return (
|
|
<div className="flex h-screen w-full text-sm overflow-hidden">
|
|
<Sidebar view={view} setView={setView} expanded={sidebarExpanded} setExpanded={setSidebarExpanded} />
|
|
<div className="flex flex-col flex-1 overflow-hidden min-w-0">
|
|
<StatusBar view={view} {...ctx} />
|
|
<div className="flex-1 overflow-hidden">
|
|
{view === 'setup' && <Setup {...ctx} />}
|
|
{view === 'baseline' && <Baseline {...ctx} />}
|
|
{view === 'forecast' && <Forecast {...ctx} />}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|