/* ============================================================ Shared List / Card renderers for Outputs (Artifacts, Collections). Both take items: [{ key, name, owner, status, mod, meta }]. List reuses the Library `.lib` table; Card is a compact card grid. Row/card click → window.openDrawer(key). ============================================================ */ const OV_ART = ( ); const OV_CHECK = ( ); const OV_PROXA = ( ); const OV_STAR = ( ); function ovOpen(it) { if (it && it.key && window.openDrawer) window.openDrawer(it.key); } // Default (artifact) columns — the original OutputsList layout, unchanged. const OV_ART_COLUMNS = [ { header: 'Title', render: (it) => (
{OV_ART} {it.name} {it.meta ? · {it.meta} : null}
) }, { header: 'Owner', thClass: 'c', tdClass: 'c', render: (it) => {it.owner} }, { header: OV_STAR, thClass: 'c', tdClass: 'c', render: () => OV_CHECK }, { header: 'Status', thClass: 'c', tdClass: 'c', render: (it) => {it.status} }, { header: 'Source', thClass: 'c', tdClass: 'c lib-src', render: () => OV_PROXA }, { header: 'Modified', thClass: 'r', tdClass: 'r', render: (it) => {it.mod} }, { header: '', thClass: 'c', tdClass: 'c', render: () => ··· }, ]; // Generic list. `columns` (defaults to the artifact layout) drives what renders; // `onRowClick` (defaults to opening the DP drawer) and `rowClass` let other // surfaces (e.g. Workspace hubs) reuse the same .lib table with different data. function OutputsList({ items, columns, onRowClick, rowClass, hideHead }) { const cols = columns || OV_ART_COLUMNS; const click = onRowClick || ovOpen; return (
{!hideHead && ( {cols.map((c, i) => )} )} {items.map((it) => ( click(it)}> {cols.map((c, i) => )} ))}
{c.header}
{c.render(it)}
); } // Editable status control (used when a card's status can be changed, e.g. Projects). const DP_STATUSES = ['Requested', 'Received', 'In review', 'Complete']; function DPStatusControl({ value, onChange }) { const [open, setOpen] = React.useState(false); const ref = React.useRef(null); React.useEffect(() => { if (!open) return; const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; document.addEventListener('mousedown', onDoc); return () => document.removeEventListener('mousedown', onDoc); }, [open]); return (
e.stopPropagation()}> {open && (
{DP_STATUSES.map((o) => ( ))}
)}
); } // THE data-product card — one component, used everywhere (Dashboard/Artifacts, // Projects). Fields drive what renders; `onClick` makes it open a drawer, // `onStatus` makes the Status editable. Missing narrative → "Click to add content". function DPCard({ item, onClick, onStatus }) { const hasAv = item.owner && item.owner !== '—'; return (
{item.num ?
{item.num}
: null}
{item.name}
{item.date ? {item.date} : null}
{item.narrative ?
:
Click to add content
} {(item.cardMeta || item.meta) ?
{item.cardMeta || item.meta}
: null}
Owner
{hasAv ? {item.owner} : null} {item.ownerName || (hasAv ? '' : '—')}
Status
{onStatus ? : {item.status}}
); } // Card view = a grid of DPCards (opens the drawer on click). function OutputsCards({ items }) { return (
{items.map((it) => ovOpen(it)} />)}
); } // The Finance hub's artifacts (mirrors the dashboard cards) — used by both the // list view and the Collections/Finance card view, so cards match the Artifacts page. window.FIN_ARTIFACTS = [ { key: 'fin_summary', num: 'FIN-00', name: 'Executive Summary', date: 'Jul 12, 2026', owner: 'LP', ownerName: 'Linda Petrov', status: 'Validated', mod: '1 d', cardMeta: 'Company overview · the context every other product reads', narrative: 'Cascade Ridge Foods is a mid-market contract food manufacturer — 9 lines across Portland, OR and Lebanon, TN, co-packing natural snacks for ~55 CPG brands. FY2025 revenue $142.0M (+10.9%). The live story: recovery from the H2-25 commodity spike, Summit Trail’s churn offset by the Verdant Valley ramp, and the LEB-L5 line upgrade.' }, { key: 'fin_pnl', num: 'FIN-01', name: 'P&L & Margin Bridge', date: 'Jul 12, 2026', owner: 'JP', ownerName: 'Jose Patel', status: 'Published', mod: '1 d', cardMeta: 'Synced 1d ago · 2 source files', narrative: 'FY2025 closed at $142.0M, up 10.9% — but the H2-25 oat & almond spike cut gross margin from 20.5% to 16.0% before pass-through pricing recovered it to 20.9% in H1-26. Q2-26 printed $43.9M at 22.8%, the strongest quarter on record.' }, { key: 'fin_model', num: 'FIN-02', name: 'Consolidated Statements', date: 'Jul 12, 2026', owner: 'TG', ownerName: 'Trevor Garcia', status: 'Published', mod: '1 d', cardMeta: 'Three-statement model · ties to the penny', narrative: 'The balance sheet balances in all 33 periods and cash flow ties to balance-sheet cash to the penny. FY2026E lands at $160.0M / 5.7% net margin, building to $191.8M by FY2028E with cash growing from $6.5M to $32.7M.' }, { key: 'fin_wc', num: 'FIN-03', name: 'Working Capital & AR', date: 'Jul 12, 2026', owner: 'TG', ownerName: 'Trevor Garcia', status: 'Published', mod: '1 d', cardMeta: 'AR aging · DSO', narrative: 'Open AR stands at $22.59M (47 days DSO) across 69 invoices — $18.3M current, $4.3M inside 30 days past due, nothing older. The one real stress, Summit Trail Snacks, was written off at $1.81M in Aug-25. Largest overdue balance: Verdant Valley at $1.4M — onboarding friction on the new anchor.' }, { key: 'fin_budget', num: 'FIN-04', name: 'Budget vs Actual', date: 'Jul 12, 2026', owner: 'JP', ownerName: 'Jose Patel', status: 'Published', mod: '1 d', cardMeta: 'H1-2026 · plan vs actual', narrative: 'H1-2026 revenue of $78.0M beat plan by +2.9%, but on a mix shift: turnkey +15.4%, tolling -34.6%. Ingredients ran +6.5% over plan on the commodity tail; opex held under plan.' }, { key: 'fin_customers', num: 'FIN-05', name: 'Customer Concentration & Revenue Build', date: 'Jul 12, 2026', owner: 'JP', ownerName: 'Jose Patel', status: 'Published', mod: '1 d', cardMeta: 'Revenue build · concentration', narrative: 'Top 10 customers = 56.4% of revenue; largest is Peak & Prairie at 10.4%. Summit Trail churned mid-2025; Verdant Valley is ramping to ~$18M/yr, the H1-26 growth engine.' }, ]; // Eyebrow = the canonical unique DP id (same id the card, drawer, and // library all show). Names may repeat; the id never collides. window.FIN_ARTIFACTS.forEach(function (a) { a.num = window.dpId(a.key); }); Object.assign(window, { OutputsList, OutputsCards, DPCard });