import { useState, useEffect, useCallback } from 'react' import useTheme from '../theme.jsx' import useAuth from '../auth.jsx' export default function StatusBar({ view, sources = [], sourceId, setSourceId, versions = [], versionId }) { const { dark, setDark } = useTheme() const { user, logout } = useAuth() const showVersion = view === 'baseline' || view === 'forecast' const selectedVersion = versions.find(v => String(v.id) === String(versionId)) const [info, setInfo] = useState(null) const [showInfo, setShow] = useState(false) const [copied, setCopied] = useState(false) // Forecast.jsx mirrors the pivot's own updating state onto this event. The // viewer shows the same thing as a beaker in its settings panel, which is // exactly where you cannot see it once the panel is closed. const [busy, setBusy] = useState(false) const refreshInfo = useCallback(async () => { if (!versionId || !showVersion) { setInfo(null); return } try { const r = await fetch(`/api/versions/${versionId}/table-info`) setInfo(r.ok ? await r.json() : null) } catch { setInfo(null) } }, [versionId, showVersion]) useEffect(() => { refreshInfo() }, [refreshInfo]) // operations broadcast this after a write so the row count stays honest useEffect(() => { const onChange = () => refreshInfo() window.addEventListener('pf-data-changed', onChange) return () => window.removeEventListener('pf-data-changed', onChange) }, [refreshInfo]) useEffect(() => { const onBusy = (e) => setBusy(!!e.detail?.busy) window.addEventListener('pf-pivot-busy', onBusy) return () => window.removeEventListener('pf-pivot-busy', onBusy) }, []) async function copyTable() { if (!info?.fc_table) return try { await navigator.clipboard.writeText(info.fc_table) setCopied(true) setTimeout(() => setCopied(false), 1200) } catch {} } const fmt = (n) => n == null ? '—' : n.toLocaleString() return (
{/* Source is a Setup concern: the raw table name means nothing to someone editing a forecast, and a version carries its own source, so picking a forecast in the sidebar settles it everywhere else. */} {view === 'setup' && ( <> Source )} {showVersion && ( <> {/* What is open, and how big it is -- the two things a forecaster wants from a status bar. The physical table it writes to is an implementation detail and means nothing to a sales account, so it has moved into the hover panel and onto the Baseline page, which is where a version's plumbing is managed. */} {info && ( <> | setShow(true)} onMouseLeave={() => setShow(false)} className="font-medium text-gray-700 cursor-default" title="Hover for the write target and row breakdown" > {selectedVersion?.name || '—'} {info.exists ? `${fmt(info.rows)} rows` : 'not created'} {showInfo && (
Write target
table {/* Still copyable, just no longer the headline: pasting it into psql is the reason it was ever on screen. */} reads from {info.source} total rows {fmt(info.rows)}
{info.by_iter?.length > 0 && ( <>
Rows by iter
{info.by_iter.map(r => ( ))}
{r.pf_iter} {fmt(r.n)}
)}
)} )} )} {/* Left of the ml-auto block, so it sits beside the pivot's own figures rather than out among the account controls. Occupies no space at all when idle: a permanently reserved slot reads as a thing that is broken when empty. */} {busy && view === 'forecast' && ( Calculating… )}
{user && ( <> {user.display_name || user.username} )}
) }