// Collection (overview dashboard) page — Cedar Narrows Water Supply Tunnel. // Built from awst_gp_meeting_13_overview.json. Pure Proxa DS. const { useState: useStateCol } = React; // Special prompt: when a user submits this from the empty-record state we // simulate the agent generating the Cedar Narrows WST Project Overview dashboard. window.DASHBOARD_GEN_PROMPT = 'Create a project dashboard from hub knowledge base'; window.DASHBOARD_GEN_STAGES = [ 'Scanning sources in this hub\u2026', 'Reading Monthly Report No. 50 \u00b7 March 2026\u2026', 'Extracting data points: safety, progress, finance\u2026', 'Validating numbers against weekly + daily reports\u2026', 'Structuring sections and KPI tiles\u2026', 'Composing the interactive dashboard\u2026', ]; /* ===== Top bar ===== */ function CollTopBar({ onBack, onOpenRecord, onOpenGraph }) { const [menuOpen, setMenuOpen] = useStateCol(false); const wrapRef = React.useRef(null); React.useEffect(() => { if (!menuOpen) return; const onDoc = (e) => { if (wrapRef.current && !wrapRef.current.contains(e.target)) setMenuOpen(false); }; const onKey = (e) => {if (e.key === 'Escape') setMenuOpen(false);}; document.addEventListener('mousedown', onDoc); document.addEventListener('keydown', onKey); return () => { document.removeEventListener('mousedown', onDoc); document.removeEventListener('keydown', onKey); }; }, [menuOpen]); const handleGraph = () => {setMenuOpen(false);if (onOpenGraph) onOpenGraph();}; return (
Traylor Bros., Inc. · Content Library
Cedar Narrows Water Supply Tunnel
James Anderson · Marketing Team · 4 min ago
{menuOpen &&
}
); } /* ===== Sidebar — sections nav ===== */ const SECTIONS = [ { num: 1, key: 'overview', label: 'Overview' }, { num: 2, key: 'monthly', label: 'Monthly reports', sub: '3 items' }, { num: 3, key: 'weekly', label: 'Weekly reports', sub: '3 items' }, { num: 4, key: 'daily', label: 'Daily reports', sub: '438 items' }, { num: 5, key: 'photos', label: 'Photos', sub: '6 items' }, { key: 'all', label: 'All items', sub: '526 items' }]; function CollSidebar({ active, onPick }) { return ( ); } /* ===== Inline charts ===== */ function AreaChart({ values, labels, tone = 'neutral', height = 120 }) { const w = 280; const h = height; const pad = { top: 12, right: 8, bottom: 22, left: 8 }; const max = Math.max(...values, 1); const min = Math.min(...values, 0); const range = max - min || 1; const xs = values.map((_, i) => pad.left + i * (w - pad.left - pad.right) / Math.max(values.length - 1, 1)); const ys = values.map((v) => pad.top + (h - pad.top - pad.bottom) * (1 - (v - min) / range)); const line = xs.map((x, i) => `${i === 0 ? 'M' : 'L'} ${x.toFixed(1)} ${ys[i].toFixed(1)}`).join(' '); const area = `${line} L ${xs[xs.length - 1].toFixed(1)} ${(h - pad.bottom).toFixed(1)} L ${xs[0].toFixed(1)} ${(h - pad.bottom).toFixed(1)} Z`; const stroke = tone === 'pos' ? '#52B26E' : tone === 'neg' ? '#D04A3F' : '#A09792'; const fill = tone === 'pos' ? 'rgba(82,178,110,0.18)' : tone === 'neg' ? 'rgba(208,74,63,0.16)' : 'rgba(160,151,146,0.18)'; return ( {labels && {labels.map((lbl, i) => {lbl} )} } ); } function BarChart({ values, labels, highlightedIndices = [], height = 140 }) { const w = 320; const h = height; const pad = { top: 12, right: 4, bottom: 24, left: 4 }; const max = Math.max(...values, 1); const n = values.length; const slot = (w - pad.left - pad.right) / n; const barW = Math.min(slot * 0.6, 26); const innerH = h - pad.top - pad.bottom; return ( {values.map((v, i) => { const x = pad.left + i * slot + (slot - barW) / 2; const bh = v / max * innerH; const y = pad.top + (innerH - bh); const hi = highlightedIndices.includes(i); return ( ); })} {labels && {labels.map((lbl, i) => {lbl} )} } ); } function GroupedBarChart({ groups, series, height = 160 }) { // groups: ['Original estimate', ...]; series: [{key,label,values,tone}] const w = 320; const h = height; const pad = { top: 12, right: 4, bottom: 26, left: 4 }; const max = Math.max(...series.flatMap((s) => s.values), 1); const groupCount = groups.length; const groupSlot = (w - pad.left - pad.right) / groupCount; const seriesCount = series.length; const barW = Math.min(groupSlot * 0.7 / seriesCount, 16); const innerH = h - pad.top - pad.bottom; const tones = { base: '#33302E', muted: '#A09792', lightMuted: '#D5CFCB' }; return ( {groups.map((g, gi) => { const groupX = pad.left + gi * groupSlot; const cluster = barW * seriesCount + (seriesCount - 1) * 2; const startX = groupX + (groupSlot - cluster) / 2; return ( {series.map((s, si) => { const v = s.values[gi] || 0; const bh = v / max * innerH; const x = startX + si * (barW + 2); const y = pad.top + (innerH - bh); return ; })} {g} ); })} ); } /* ===== Headline with a single colored accent phrase ===== */ function HeadlineText({ text, accent, color }) { if (!accent || !text || !text.includes(accent)) return text || null; const idx = text.indexOf(accent); return ( {text.slice(0, idx)} {accent} {text.slice(idx + accent.length)} ); } /* ===== Rich trend chart (gridlines, right axis, goal band, annotations) ===== */ function RichTrendChart({ values, xLabels, yMax, yTicks, color = '#D04A3F', fill = 'rgba(208,74,63,0.12)', area = false, goal = null, goalLabel = '', zoneFill = null, annotations = [], height = 280 }) { const W = 460, H = height; const pad = { top: 22, right: 42, bottom: 30, left: 14 }; const innerW = W - pad.left - pad.right; const innerH = H - pad.top - pad.bottom; const n = values.length; const x = (i) => pad.left + (n <= 1 ? innerW / 2 : i * innerW / (n - 1)); const y = (v) => pad.top + innerH * (1 - Math.max(0, Math.min(v, yMax)) / yMax); const linePath = values.map((v, i) => `${i === 0 ? 'M' : 'L'} ${x(i).toFixed(1)} ${y(v).toFixed(1)}`).join(' '); const areaPath = `${linePath} L ${x(n - 1).toFixed(1)} ${y(0).toFixed(1)} L ${x(0).toFixed(1)} ${y(0).toFixed(1)} Z`; return ( {yTicks.map((t, i) => {t} )} {zoneFill && goal != null && } {area && } {goal != null && {goalLabel && {goalLabel}} } {values.map((v, i) => )} {xLabels.map((lbl, i) => {lbl})} {annotations.map((a, i) => { const px = x(a.i), py = y(values[a.i]); const below = a.place === 'below'; const v1 = below ? py + 26 : py - 30; const t1 = v1 + 14; const cFrom = below ? py + 7 : py - 7; const cTo = below ? py + 17 : py - 19; const anchor = px > pad.left + innerW - 70 ? 'end' : (px < pad.left + 70 ? 'start' : 'middle'); return ( {a.value} {a.title} ); })} ); } /* ===== Analytical chart card (bold headline intro + chart + legend) ===== */ function AnalysisCard({ headline, accent, accentColor, sub, children, legend }) { return (
{sub &&
{sub}
}
{children}
{legend &&
{legend.map((l, i) => {l.label} )}
}
); } /* ===== KPI Edit Side Panel ===== */ function KpiEditPanel({ open, kpi, onClose, onChange }) { if (!open || !kpi) return null; const update = (k, v) => onChange({ ...kpi, [k]: v }); return (