Compare commits
1 Commits
master
...
fix/stale-
| Author | SHA1 | Date | |
|---|---|---|---|
| e6e2faeb37 |
@ -112,7 +112,10 @@ Theme state lives in `ui/src/theme.jsx` — a React context (`ThemeContext`) wit
|
|||||||
- Operation panel (Scale/Recode/Clone) SQL generation and dim_period JOIN are complete; UI wiring to API still needs completion
|
- Operation panel (Scale/Recode/Clone) SQL generation and dim_period JOIN are complete; UI wiring to API still needs completion
|
||||||
- Load progress bar is jittery — needs throttle (~10 updates/sec)
|
- Load progress bar is jittery — needs throttle (~10 updates/sec)
|
||||||
- Default pivot layout should be configurable per source (currently hardcodes first 2 dimensions)
|
- Default pivot layout should be configurable per source (currently hardcodes first 2 dimensions)
|
||||||
- Source/version selection doesn't persist across page reload
|
- Source/version selection persists in `localStorage` (`pf_sourceId` / `pf_versionId`,
|
||||||
|
`App.jsx`). It is re-validated against the live list whenever that list changes, so a
|
||||||
|
deregistered source or deleted version re-points at the first remaining one instead of
|
||||||
|
leaving a dead id that 404s every call
|
||||||
- Col_meta / version schema drift: if col_meta roles change after a version's forecast table is created, SQL and DDL go out of sync — workaround is to delete and recreate the version
|
- Col_meta / version schema drift: if col_meta roles change after a version's forecast table is created, SQL and DDL go out of sync — workaround is to delete and recreate the version
|
||||||
|
|
||||||
## Deferred (not in v1)
|
## Deferred (not in v1)
|
||||||
|
|||||||
@ -10,6 +10,7 @@ export default function App() {
|
|||||||
const [sidebarExpanded, setSidebarExpanded] = useState(() => localStorage.getItem('pf_sidebar') !== 'collapsed')
|
const [sidebarExpanded, setSidebarExpanded] = useState(() => localStorage.getItem('pf_sidebar') !== 'collapsed')
|
||||||
|
|
||||||
const [sources, setSources] = useState([])
|
const [sources, setSources] = useState([])
|
||||||
|
const [sourcesLoaded, setSourcesLoaded] = useState(false)
|
||||||
const [sourceId, setSourceId] = useState(() => localStorage.getItem('pf_sourceId') || '')
|
const [sourceId, setSourceId] = useState(() => localStorage.getItem('pf_sourceId') || '')
|
||||||
const [versions, setVersions] = useState([])
|
const [versions, setVersions] = useState([])
|
||||||
const [versionId, setVersionId] = useState(() => localStorage.getItem('pf_versionId') || '')
|
const [versionId, setVersionId] = useState(() => localStorage.getItem('pf_versionId') || '')
|
||||||
@ -21,37 +22,49 @@ export default function App() {
|
|||||||
|
|
||||||
const refreshSources = useCallback(async () => {
|
const refreshSources = useCallback(async () => {
|
||||||
const data = await fetch('/api/sources').then(r => r.json())
|
const data = await fetch('/api/sources').then(r => r.json())
|
||||||
setSources(data)
|
const list = Array.isArray(data) ? data : []
|
||||||
return data
|
setSources(list)
|
||||||
|
setSourcesLoaded(true)
|
||||||
|
return list
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const refreshVersions = useCallback(async (sid) => {
|
const refreshVersions = useCallback(async (sid) => {
|
||||||
const id = sid ?? sourceId
|
const id = sid ?? sourceId
|
||||||
if (!id) { setVersions([]); return [] }
|
if (!id) { setVersions([]); return [] }
|
||||||
const data = await fetch(`/api/sources/${id}/versions`).then(r => r.json())
|
const data = await fetch(`/api/sources/${id}/versions`).then(r => r.json())
|
||||||
setVersions(data)
|
const list = Array.isArray(data) ? data : []
|
||||||
return data
|
setVersions(list)
|
||||||
|
return list
|
||||||
}, [sourceId])
|
}, [sourceId])
|
||||||
|
|
||||||
|
useEffect(() => { refreshSources() }, [])
|
||||||
|
|
||||||
|
// The selection is restored from localStorage and survives a deregister, so it
|
||||||
|
// has to be re-checked against the list itself rather than only at mount:
|
||||||
|
// deleting the selected source otherwise leaves a dead id behind and every
|
||||||
|
// call 404s "Source not found" until the page is reloaded.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
refreshSources().then(data => {
|
if (!sourcesLoaded) return
|
||||||
if (data.length === 0) { setSourceId(''); return }
|
if (sources.length === 0) { setSourceId(''); return }
|
||||||
if (!sourceId || !data.some(s => String(s.id) === String(sourceId))) {
|
if (!sourceId || !sources.some(s => String(s.id) === String(sourceId))) {
|
||||||
setSourceId(String(data[0].id))
|
setSourceId(String(sources[0].id))
|
||||||
}
|
}
|
||||||
})
|
}, [sources, sourcesLoaded, sourceId])
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!sourceId) { setVersions([]); setVersionId(''); return }
|
if (!sourceId) { setVersions([]); setVersionId(''); return }
|
||||||
refreshVersions(sourceId).then(data => {
|
refreshVersions(sourceId)
|
||||||
if (data.length === 0) { setVersionId(''); return }
|
|
||||||
if (!versionId || !data.some(v => String(v.id) === String(versionId))) {
|
|
||||||
setVersionId(String(data[0].id))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, [sourceId])
|
}, [sourceId])
|
||||||
|
|
||||||
|
// Same reasoning as sources: a deleted version must not stay selected.
|
||||||
|
useEffect(() => {
|
||||||
|
if (!sourceId) return
|
||||||
|
if (versions.length === 0) { setVersionId(''); return }
|
||||||
|
if (!versionId || !versions.some(v => String(v.id) === String(versionId))) {
|
||||||
|
setVersionId(String(versions[0].id))
|
||||||
|
}
|
||||||
|
}, [versions, sourceId, versionId])
|
||||||
|
|
||||||
const ctx = {
|
const ctx = {
|
||||||
sources, sourceId, setSourceId,
|
sources, sourceId, setSourceId,
|
||||||
versions, versionId, setVersionId,
|
versions, versionId, setVersionId,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user