// Screen: Reports — five fully-rendered, interactive reports + grayed builder. // Charts lazy-mount: only the active report (and active sub-view) is rendered, // so nothing is drawn into a hidden container. const REPORT_TYPES = [ { id: 'prescriber', n: '01', title: 'Prescriber Reports', desc: 'Accounts, trends & actions' }, { id: 'revenue', n: '02', title: 'Revenue & Growth', desc: 'MTD/QTD/YTD · YoY · mix' }, { id: 'patients', n: '03', title: 'Patient Analytics', desc: 'Aggregate · PHI-safe' }, { id: 'volume', n: '04', title: 'Volume & Workflow', desc: 'Fills · turnaround · staff' }, { id: 'controlled', n: '05', title: 'Controlled Substances', desc: 'C-III–C-V · PDMP' }, ]; const fmtCount = n => Math.round(n).toLocaleString('en-US'); const fmtM = n => '$' + (n / 1e6).toFixed(2) + 'M'; // ---- Small local viz primitives ---------------------------------------- function Donut({ data, size = 168, thickness = 26, title, sub }) { const total = data.reduce((a, d) => a + d.val, 0); const r = (size - thickness) / 2; const c = 2 * Math.PI * r; let off = 0; return ( ); } function DonutBlock({ data, title, sub, fmt }) { const total = data.reduce((a, d) => a + d.val, 0); const f = fmt || fmtCount; return (
{data.map((d, i) => (
{d.name} {f(d.val)} {(d.val / total * 100).toFixed(1)}%
))}
); } function HBars({ data, fmt }) { const max = Math.max(...data.map(d => d.val)); const f = fmt || fmtCount; return (
{data.map((d, i) => (
{d.name} {d.sub ? {d.sub} : null}
{f(d.val)}
))}
); } function Kpis({ items }) { return (
{items.map((it, i) => (
{it.k}
{it.v}
{it.sub ?
{it.sub}
: null}
))}
); } function PeriodSel({ value, onChange, opts }) { return (
{opts.map(o => ( ))}
); } function ReportHead({ title, sub, period, children }) { return (

{title}

{sub ?
{sub}
: null}
{children}
); } function ChangeTag({ pct }) { const cls = pct > 1.5 ? 'up' : pct < -1.5 ? 'down' : 'flat'; const arrow = cls === 'up' ? '▲' : cls === 'down' ? '▼' : '—'; return {arrow} {Math.abs(pct).toFixed(1)}%; } // Least-squares fitted line for a dashed trend overlay. function linfit(data) { const n = data.length; let sx = 0, sy = 0, sxx = 0, sxy = 0; for (let i = 0; i < n; i++) { sx += i; sy += data[i]; sxx += i * i; sxy += i * data[i]; } const b = (n * sxy - sx * sy) / (n * sxx - sx * sx); const a = (sy - b * sx) / n; return data.map((_, i) => +(a + b * i).toFixed(1)); } // ---- Report 1: Prescriber Reports -------------------------------------- function PrescriberReport({ onPdf }) { const { useState } = React; const [period, setPeriod] = useState('90d'); const [selId, setSelId] = useState(null); const [sortKey, setSortKey] = useState('rev'); const months = PRESC_PERIODS.find(p => p.key === period).months; const rows = PRESCRIBERS.map(c => { const rev = prescRev(c, months); const chg = prescChange(c, months); return { c, rev, chg }; }); const sorted = [...rows].sort((a, b) => sortKey === 'rev' ? b.rev - a.rev : b.chg - a.chg); const growing = rows.filter(r => r.chg > 1.5).length; const declining = rows.filter(r => r.chg < -1.5).length; const totalRev = rows.reduce((a, r) => a + r.rev, 0); if (selId) return ; const totalMonths = MONTHS_12.map((_, i) => +(PRESCRIBERS.reduce((a, c) => a + c.months[i], 0) / 1000).toFixed(1)); const series = [ { name: 'Total', data: totalMonths, color: 'var(--moss)', bold: true }, { name: '', data: linfit(totalMonths), color: '#8e9385', dash: true }, ]; return (
i % 2 === 0 ? m : '')}>
{sorted.map(({ c, rev, chg }) => ( setSelId(c.id)}> ))}
Prescribing clinic Top category setSortKey('rev')}>Revenue ▾ setSortKey('chg')}>Recent vs prior ▾
{c.clinic}
{c.prescriber} · {c.city}
{c.topCat} {fmtK(rev)}
); } function genWeekly(clinic, period) { const months = PRESC_PERIODS.find(p => p.key === period).months; const monthlyRev = clinic.months[clinic.months.length - 1]; const sign = clinic.trend === 'up' ? 1 : clinic.trend === 'down' ? -1 : 0; const labels = ['May 11', 'May 18', 'May 25', 'Jun 1', 'Jun 8']; const baseScripts = clinic.scripts30 / 4.345; const baseRev = monthlyRev / 4.345; return labels.map((lb, i) => { const g = 1 + sign * (i - 2) * 0.03; return { week: lb, scripts: Math.round(baseScripts * g), rev: Math.round(baseRev * g / 10) * 10, newPts: Math.max(0, clinic.newPerWeek + ((i % 2 === 0) ? 0 : (sign >= 0 ? 1 : -1))), }; }); } function PrescriberSingle({ id, setId, period, setPeriod, onPdf }) { const idx = PRESCRIBERS.findIndex(c => c.id === id); const c = PRESCRIBERS[idx]; const months = PRESC_PERIODS.find(p => p.key === period).months; const rev = prescRev(c, months); const scripts = Math.round(c.scripts30 * rev / c.months[c.months.length - 1]); const avg = rev / scripts; const series = [ { name: c.prescriber.split(' ').slice(-1)[0], data: c.months.map(v => +(v / 1000).toFixed(1)), color: 'var(--moss)', bold: true }, { name: '', data: linfit(c.months.map(v => +(v / 1000).toFixed(1))), color: '#8e9385', dash: true }, ]; const weeks = genWeekly(c, period); const prev = () => setId(PRESCRIBERS[(idx - 1 + PRESCRIBERS.length) % PRESCRIBERS.length].id); const next = () => setId(PRESCRIBERS[(idx + 1) % PRESCRIBERS.length].id); return (
{PRESCRIBERS.map(p => ( ))}
Account

{c.clinic}

{c.prescriber} · {c.city}
Revenue · {period}
{fmtK(rev)}
Scripts
{fmtCount(scripts)}
Avg / script
{fmtMoney(avg)}
Top category
{c.topCat}
i % 2 === 0 ? m : '')}>
Trend analysis

{c.analysis}

Recommended action {c.rec}
{weeks.map((w, i) => ( ))}
WeekScriptsRevenueNew patients
{w.week} {w.scripts} {fmtMoney(w.rev)} {w.newPts}
); } // ---- Report 2: Revenue & Growth ---------------------------------------- function RevenueReport({ onPdf }) { const series = [ { name: 'This yr', data: REV_THIS_YEAR, color: 'var(--moss)', bold: true }, { name: 'Last yr', data: REV_LAST_YEAR, color: '#8e9385' }, ]; const movers = [...BIGGEST_MOVERS] .map(m => ({ ...m, pct: (m.recent - m.prior) / m.prior * 100 })) .sort((a, b) => Math.abs(b.recent - b.prior) - Math.abs(a.recent - a.prior)); return (
i % 2 === 0 ? m : '')}>
'$' + v.toFixed(1) + 'k'}>
{movers.map((m, i) => ( ))}
Item90d revenueChange
{m.name}
{m.cat}
{fmtK(m.recent)}
); } // ---- Report 3: Patient Analytics --------------------------------------- function PatientReport({ onPdf }) { const a = PATIENT_ANALYTICS; const series = [{ name: 'New', data: a.newPerMonth, color: 'var(--moss)', bold: true }]; return (
i % 2 === 0 ? m : '')} fmt={fmtCount}>
{a.gender.map((g, i) => ( ))}
GenderPatientsShare
{g[0]} {fmtCount(g[1])} {g[2]}
); } // ---- Report 4: Volume & Workflow --------------------------------------- function VolumeReport({ onPdf }) { const { useState } = React; const [period, setPeriod] = useState('30d'); const v = VOLUME; const fs = fillsSeries(period); const series = [{ name: 'Fills', data: fs.data, color: 'var(--moss)', bold: true }]; const bars = v.pharmacists.map(p => ({ name: p.name, sub: p.store, val: p.fills })); return (
p.key === period).label + ' · stores closed weekends'}>
{v.callout}
); } // ---- Report 5: Controlled Substance Compliance ------------------------- function ControlledReport({ onPdf }) { const k = CONTROLLED; const series = [{ name: 'Controlled', data: k.perMonth, color: 'var(--moss)', bold: true }]; const schedTone = { 'C-III': 'warn', 'C-IV': 'neutral', 'C-V': 'ok' }; return (
i % 2 === 0 ? m : '')} fmt={fmtCount}>
{k.items.map((it, i) => ( ))}
ItemScheduleFills · YTDLast dispensed
{it.name} {it.sched} {fmtCount(it.fills)} {it.lastStore}
{k.pdmp}
); } // ---- Shell ------------------------------------------------------------- function Reports() { const { useState } = React; const [active, setActive] = useState('prescriber'); const [toast, showToast] = useToast(); const onPdf = () => showToast('Available in full build.'); const pane = { prescriber: , revenue: , patients: , volume: , controlled: , }[active]; return (
{REPORT_TYPES.map(r => ( ))}
06 Custom builder Phase 2 Drag-and-drop report designer
{pane}
Aggregate data only · PHI-safe
); } Object.assign(window, { Reports });