diff --git a/ui/src/App.jsx b/ui/src/App.jsx
index e4d4e3f..1ed8ba1 100644
--- a/ui/src/App.jsx
+++ b/ui/src/App.jsx
@@ -1,4 +1,4 @@
-import { useState, useEffect } from 'react'
+import { useState, useEffect, useCallback } from 'react'
import Sidebar from './components/Sidebar.jsx'
import StatusBar from './components/StatusBar.jsx'
import Setup from './views/Setup.jsx'
@@ -9,20 +9,66 @@ 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 (
-
+
- {view === 'setup' && }
- {view === 'baseline' && }
- {view === 'forecast' && }
+ {view === 'setup' && }
+ {view === 'baseline' && }
+ {view === 'forecast' && }
)
-}
\ No newline at end of file
+}
diff --git a/ui/src/components/StatusBar.jsx b/ui/src/components/StatusBar.jsx
index d1a62dc..3da49cf 100644
--- a/ui/src/components/StatusBar.jsx
+++ b/ui/src/components/StatusBar.jsx
@@ -1,21 +1,46 @@
import useTheme from '../theme.jsx'
-export default function StatusBar() {
+export default function StatusBar({ view, sources = [], sourceId, setSourceId, versions = [], versionId, setVersionId }) {
const { dark, setDark } = useTheme()
+ const showVersion = view === 'baseline' || view === 'forecast'
+ const selectedVersion = versions.find(v => String(v.id) === String(versionId))
return (
-
+
Source
-
sales_orders
-
|
-
Version
-
FY2026 Plan
-
|
-
Baseline
-
44,313 rows
-
|
-
Status
-
open
+
+
+ {showVersion && (
+ <>
+
|
+
Version
+
+ {selectedVersion && (
+
+ {selectedVersion.status}
+
+ )}
+ >
+ )}
+
)
-}
\ No newline at end of file
+}
diff --git a/ui/src/views/Baseline.jsx b/ui/src/views/Baseline.jsx
index e62f762..c49dcc0 100644
--- a/ui/src/views/Baseline.jsx
+++ b/ui/src/views/Baseline.jsx
@@ -55,11 +55,7 @@ function emptyFilter(cols) {
return { col: cols[0]?.cname || '', op: 'BETWEEN', values: ['', ''] }
}
-export default function Baseline() {
- const [sources, setSources] = useState([])
- const [sourceId, setSourceId] = useState('')
- const [versions, setVersions] = useState([])
- const [versionId, setVersionId] = useState('')
+export default function Baseline({ sources = [], sourceId, versions = [], versionId, setVersionId, refreshVersions }) {
const [filterCols, setFilterCols] = useState([])
const [log, setLog] = useState([])
@@ -81,20 +77,8 @@ export default function Baseline() {
const [expandedId, setExpandedId] = useState(null)
const [msg, setMsg] = useState(null)
- useEffect(() => {
- fetch('/api/sources').then(r => r.json()).then(data => {
- setSources(data)
- if (data.length > 0) setSourceId(String(data[0].id))
- })
- }, [])
-
useEffect(() => {
if (!sourceId) return
- fetch(`/api/sources/${sourceId}/versions`).then(r => r.json()).then(data => {
- setVersions(data)
- if (data.length > 0) setVersionId(String(data[0].id))
- else setVersionId('')
- })
fetch(`/api/sources/${sourceId}/cols`).then(r => r.json()).then(cols => {
const fc = cols.filter(c => c.role === 'date' || c.role === 'filter')
setFilterCols(fc)
@@ -124,8 +108,7 @@ export default function Baseline() {
})
const data = await res.json()
if (!res.ok) { flash(data.error, 'error'); return }
- const updated = await fetch(`/api/sources/${sourceId}/versions`).then(r => r.json())
- setVersions(updated)
+ await refreshVersions(sourceId)
setVersionId(String(data.id))
setShowNewVersion(false)
setNewVerName('')
@@ -220,8 +203,7 @@ export default function Baseline() {
})
const data = await res.json()
if (!res.ok) { flash(data.error, 'error'); return }
- const updated = await fetch(`/api/sources/${sourceId}/versions`).then(r => r.json())
- setVersions(updated)
+ await refreshVersions(sourceId)
flash('Version closed')
}
@@ -229,8 +211,7 @@ export default function Baseline() {
const res = await fetch(`/api/versions/${versionId}/reopen`, { method: 'POST' })
const data = await res.json()
if (!res.ok) { flash(data.error, 'error'); return }
- const updated = await fetch(`/api/sources/${sourceId}/versions`).then(r => r.json())
- setVersions(updated)
+ await refreshVersions(sourceId)
flash('Version reopened')
}
@@ -239,8 +220,7 @@ export default function Baseline() {
const res = await fetch(`/api/versions/${versionId}`, { method: 'DELETE' })
const data = await res.json()
if (!res.ok) { flash(data.error, 'error'); return }
- const updated = await fetch(`/api/sources/${sourceId}/versions`).then(r => r.json())
- setVersions(updated)
+ const updated = await refreshVersions(sourceId)
setVersionId(updated.length > 0 ? String(updated[0].id) : '')
flash('Version deleted')
}
@@ -264,33 +244,13 @@ export default function Baseline() {
)}
- {/* Source + Version bar */}
+ {/* Version actions */}
-
- Source
-
-
-
- Version
-
- {versionId && (
-
- {selectedVersion?.status}
-
- )}
-
{versionId && (
-
+
{selectedVersion?.status === 'open'
?
:
diff --git a/ui/src/views/Forecast.jsx b/ui/src/views/Forecast.jsx
index 3017291..1f17dc9 100644
--- a/ui/src/views/Forecast.jsx
+++ b/ui/src/views/Forecast.jsx
@@ -29,12 +29,8 @@ function cleanLayout(cfg, validCols) {
return c
}
-export default function Forecast() {
+export default function Forecast({ sourceId, versionId }) {
const { dark } = useTheme()
- const [sources, setSources] = useState([])
- const [sourceId, setSourceId] = useState('')
- const [versions, setVersions] = useState([])
- const [versionId, setVersionId] = useState('')
const [loading, setLoading] = useState(false)
const [largeDataset, setLargeDataset] = useState(false)
const [loadProgress, setLoadProgress] = useState(null) // { received, total }
@@ -86,21 +82,6 @@ export default function Forecast() {
window.addEventListener('mouseup', onUp)
}
- useEffect(() => {
- fetch('/api/sources').then(r => r.json()).then(data => {
- setSources(data)
- if (data.length > 0) setSourceId(String(data[0].id))
- })
- }, [])
-
- useEffect(() => {
- if (!sourceId) return
- fetch(`/api/sources/${sourceId}/versions`).then(r => r.json()).then(data => {
- setVersions(data)
- setVersionId(data.length > 0 ? String(data[0].id) : '')
- })
- }, [sourceId])
-
useEffect(() => {
if (!versionId || !sourceId) return
loadLayouts(versionId)
@@ -420,36 +401,12 @@ export default function Forecast() {
}
}
- const selectedVersion = versions.find(v => String(v.id) === versionId)
const dimCols = colMetaRef.current.filter(c => c.role === 'dimension')
const hasSlice = Object.keys(slice).length > 0
return (
- {/* Source / version bar */}
-
-
- Source
-
-
-
- Version
-
- {selectedVersion && (
-
- {selectedVersion.status}
-
- )}
-
-
-
{/* Toolbar */}
diff --git a/ui/src/views/Setup.jsx b/ui/src/views/Setup.jsx
index 3942c52..d267417 100644
--- a/ui/src/views/Setup.jsx
+++ b/ui/src/views/Setup.jsx
@@ -11,7 +11,7 @@ const ROLE_STYLE = {
ignore: 'bg-gray-100 text-gray-400',
}
-export default function Setup() {
+export default function Setup({ refreshSources }) {
const [tables, setTables] = useState([])
const [sources, setSources] = useState([])
const [selectedSource, setSelectedSource] = useState(null)
@@ -40,6 +40,7 @@ export default function Setup() {
})
})
}).catch(console.error)
+ refreshSources?.()
}
function selectSource(source) {