import React, { useEffect, useMemo, useState } from "react"; import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Separator } from "@/components/ui/separator"; import { Badge } from "@/components/ui/badge"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { MoreHorizontal, Copy, Plus, Trash2, RotateCcw, Download, Settings, Info, Pin, PinOff, Printer, ArrowRight, Wand2, } from "lucide-react"; import { ResponsiveContainer, LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip as ReTooltip, BarChart, Bar, Legend, } from "recharts"; // ===================== // Accelerate 3.0 — Scenario Calculator (Team Tool) // ===================== // Key rules enforced per Ken: // - Pricing tiers are ONLY Silver / Gold / Platinum (based on annual spend minimums). // - “+” levels are NOT pricing tiers; they only increase the dividend rate. // - Dividend “+” levels are fixed spend bands: // Silver+ = 15,000–19,999 // Gold+ = 30,000–34,999 // Plat+ = 50,000–74,999 // - Free goods earned quarterly by family (Silver/Gold/Platinum) with cumulative threshold gates. // - 90-day activation target = 25% of the annual minimum for the enrolled tier. // - Dividend earned on growth above PY baseline AND annual minimum. // ---------- Helpers ---------- const fmtMoney = (n) => new Intl.NumberFormat(undefined, { style: "currency", currency: "USD", maximumFractionDigits: 0, }).format(Number.isFinite(n) ? n : 0); const fmtPct = (n) => `${(Number.isFinite(n) ? n : 0).toFixed(1)}%`; const STORAGE_KEY = "accelerate3_scenarios_v2"; const SETTINGS_KEY = "accelerate3_settings_v2"; const DEFAULT_SETTINGS = { // Annual minimum spend for pricing eligibility (and enrollment tier minimum) mins: { Silver: 10000, Gold: 20000, Platinum: 35000, }, // Free goods: family rate by pricing tier family freeGoodsRate: { Silver: 0.12, Gold: 0.07, Platinum: 0.03, }, // Quarterly gates: cumulative % of annual min required by end of each quarter quarterGates: { Q1: 0.25, Q2: 0.5, Q3: 0.75, Q4: 1.0, }, // Activation: 90-day target is 25% of annual minimum activation: { targetPct: 0.25, bonusAmount: 500, // thresholds are % of target bonusThreshold: 1.0, noBonusFloor: 0.9, }, // Dividend rates (growth dividend) dividendRate: { Silver: 0.1, "Silver+": 0.14, Gold: 0.16, "Gold+": 0.18, Platinum: 0.19, "Platinum+": 0.2, }, // Dividend “+” qualification spend bands (editable) plusBands: { // inclusive lower, inclusive upper SilverPlus: { min: 15000, max: 19999 }, GoldPlus: { min: 30000, max: 34999 }, PlatinumPlus: { min: 50000, max: 74999 }, }, // Royalty milestone % by consecutive years and family royalty: { years: [3, 5, 7, 10], rate: { 3: { Silver: 0.04, Gold: 0.05, Platinum: 0.06 }, 5: { Silver: 0.05, Gold: 0.06, Platinum: 0.07 }, 7: { Silver: 0.06, Gold: 0.07, Platinum: 0.08 }, 10: { Silver: 0.07, Gold: 0.08, Platinum: 0.09 }, }, }, pricingReview: { bufferDays: 30, }, }; function loadJSON(key, fallback) { try { const raw = localStorage.getItem(key); if (!raw) return fallback; const parsed = JSON.parse(raw); return parsed ?? fallback; } catch { return fallback; } } function saveJSON(key, value) { try { localStorage.setItem(key, JSON.stringify(value)); } catch { // ignore } } function uuid() { return Math.random().toString(16).slice(2) + Date.now().toString(16); } function inBand(x, band) { const n = Number(x || 0); return n >= band.min && n <= band.max; } function pricingTierFromAnnual(total, mins) { if (total >= mins.Platinum) return "Platinum"; if (total >= mins.Gold) return "Gold"; if (total >= mins.Silver) return "Silver"; return "Not Qualified"; } function dividendLevelFromSpend(pricingFamily, annualTotal, settings) { // “+” levels are spend bands; they boost the dividend rate only. if (pricingFamily === "Not Qualified") return "Not Qualified"; if (pricingFamily === "Silver") { return inBand(annualTotal, settings.plusBands.SilverPlus) ? "Silver+" : "Silver"; } if (pricingFamily === "Gold") { return inBand(annualTotal, settings.plusBands.GoldPlus) ? "Gold+" : "Gold"; } if (pricingFamily === "Platinum") { return inBand(annualTotal, settings.plusBands.PlatinumPlus) ? "Platinum+" : "Platinum"; } return "Not Qualified"; } function activationStatus(memberType, enrolledTier, spend90d, settings) { // 90-day activation is ONLY evaluated for NEW members. if (memberType !== "New") { return { status: "N/A (Existing member)", pct: 0, target: 0, bonus: 0, qualified: true, disqualified: false, na: true, }; } const min = settings.mins[enrolledTier]; const target = min * settings.activation.targetPct; const pct = target > 0 ? spend90d / target : 0; if (pct >= settings.activation.bonusThreshold) { return { status: "Qualified + $500 Bonus", pct, target, bonus: settings.activation.bonusAmount, qualified: true, disqualified: false, na: false, }; } if (pct >= settings.activation.noBonusFloor) { return { status: "Qualified (No Bonus)", pct, target, bonus: 0, qualified: true, disqualified: false, na: false, }; } return { status: "Disqualified (<90%)", pct, target, bonus: 0, qualified: false, disqualified: true, na: false, }; } function quarterlyFreeGoods(quarters, pricingFamily, settings) { const min = pricingFamily === "Not Qualified" ? 0 : settings.mins[pricingFamily]; const rate = settings.freeGoodsRate[pricingFamily] ?? 0; const qOrder = ["Q1", "Q2", "Q3", "Q4"]; let cumulative = 0; const out = {}; for (const q of qOrder) { const spendQ = Number(quarters[q] ?? 0); cumulative += spendQ; const gate = settings.quarterGates[q] ?? 0; const required = min * gate; const passed = cumulative >= required && min > 0; out[q] = { spendQ, cumulative, required, passed, earned: passed ? spendQ * rate : 0, }; } const totalEarned = qOrder.reduce((s, q) => s + out[q].earned, 0); return { byQuarter: out, totalEarned, rate, min }; } function growthDividend({ annualTotal, baselinePY, pricingFamily, dividendLevel, settings }) { if (pricingFamily === "Not Qualified") return { eligible: false, reason: "Below annual minimum", growth: 0, rate: 0, payout: 0 }; const min = settings.mins[pricingFamily]; if (annualTotal < min) return { eligible: false, reason: "Below annual minimum", growth: 0, rate: 0, payout: 0 }; const growth = Math.max(0, annualTotal - baselinePY); if (growth <= 0) return { eligible: false, reason: "No growth vs PY baseline", growth, rate: 0, payout: 0 }; const rate = settings.dividendRate[dividendLevel] ?? 0; return { eligible: true, reason: "Eligible", growth, rate, payout: growth * rate }; } function royaltyPayout({ annualTotal, pricingFamily, consecutiveYears, settings }) { const yearsList = settings.royalty.years; const milestone = yearsList.includes(consecutiveYears) ? consecutiveYears : null; if (!milestone) return { eligible: false, milestone: null, rate: 0, payout: 0 }; if (pricingFamily === "Not Qualified") return { eligible: false, milestone, rate: 0, payout: 0 }; const rate = settings.royalty.rate[milestone]?.[pricingFamily] ?? 0; return { eligible: rate > 0, milestone, rate, payout: annualTotal * rate }; } function tierAccent(tier) { if (tier === "Silver") return "from-sky-500/15 to-sky-500/5"; if (tier === "Gold") return "from-amber-500/15 to-amber-500/5"; if (tier === "Platinum") return "from-violet-500/15 to-violet-500/5"; return "from-slate-500/10 to-slate-500/5"; } function tierBadgeVariant(tier) { if (tier === "Not Qualified") return "secondary"; return "default"; } function computeGaps(annualTotal, pricingFamily, dividendLevel, settings) { // Next pricing tier gap const mins = settings.mins; let nextTier = null; let nextTierTarget = null; if (pricingFamily === "Not Qualified") { nextTier = "Silver"; nextTierTarget = mins.Silver; } else if (pricingFamily === "Silver") { nextTier = "Gold"; nextTierTarget = mins.Gold; } else if (pricingFamily === "Gold") { nextTier = "Platinum"; nextTierTarget = mins.Platinum; } else { nextTier = null; nextTierTarget = null; } const gapToNextTier = nextTierTarget ? Math.max(0, nextTierTarget - annualTotal) : 0; // Next dividend “+” gap (inside pricing family) let nextPlusLabel = null; let nextPlusTarget = null; if (pricingFamily === "Silver") { const band = settings.plusBands.SilverPlus; if (annualTotal < band.min) { nextPlusLabel = "Silver+"; nextPlusTarget = band.min; } else { nextPlusLabel = null; nextPlusTarget = null; } } if (pricingFamily === "Gold") { const band = settings.plusBands.GoldPlus; if (annualTotal < band.min) { nextPlusLabel = "Gold+"; nextPlusTarget = band.min; } else { nextPlusLabel = null; nextPlusTarget = null; } } if (pricingFamily === "Platinum") { const band = settings.plusBands.PlatinumPlus; if (annualTotal < band.min) { nextPlusLabel = "Platinum+"; nextPlusTarget = band.min; } else { nextPlusLabel = null; nextPlusTarget = null; } } const gapToNextPlus = nextPlusTarget ? Math.max(0, nextPlusTarget - annualTotal) : 0; return { nextTier, gapToNextTier, nextPlusLabel, gapToNextPlus }; } // ---------- Defaults ---------- function makeScenario(overrides = {}) { return { id: overrides.id ?? uuid(), name: overrides.name ?? "Scenario", memberType: overrides.memberType ?? "Existing", enrolledTier: overrides.enrolledTier ?? "Silver", // impacts 90-day target spend: { Q1: overrides.spend?.Q1 ?? 0, Q2: overrides.spend?.Q2 ?? 0, Q3: overrides.spend?.Q3 ?? 0, Q4: overrides.spend?.Q4 ?? 0, }, spend90d: overrides.spend90d ?? 0, baselinePY: overrides.baselinePY ?? 0, consecutiveYears: overrides.consecutiveYears ?? 0, notes: overrides.notes ?? "", // Compare pinned: overrides.pinned ?? false, }; } const EXAMPLES = [ makeScenario({ name: "Example — Silver office", memberType: "New", enrolledTier: "Silver", spend90d: 2600, spend: { Q1: 3200, Q2: 2800, Q3: 2500, Q4: 2200 }, baselinePY: 9000, consecutiveYears: 3, }), makeScenario({ name: "Example — Gold growth push", memberType: "Existing", enrolledTier: "Gold", spend90d: 5200, spend: { Q1: 7000, Q2: 6500, Q3: 7200, Q4: 7600 }, baselinePY: 22000, consecutiveYears: 5, }), makeScenario({ name: "Example — Platinum heavy", memberType: "Existing", enrolledTier: "Platinum", spend90d: 9500, spend: { Q1: 13000, Q2: 12000, Q3: 15000, Q4: 14000 }, baselinePY: 41000, consecutiveYears: 7, }), ]; function PrintSheet({ payload }) { // Print-only markup return (
Accelerate 3.0 — Scenario Summary
Internal use • Generated from Scenario Calculator
Scenario
{payload.name}
{payload.tiles.map((t) => (
{t.label}
{t.value}
{t.sub ?
{t.sub}
: null}
))}
Inputs
Member type: {payload.memberType}
Enrolled tier (90-day): {payload.enrolledTier}
90-day spend: {fmtMoney(payload.spend90d)}
PY baseline: {fmtMoney(payload.baselinePY)}
Consecutive years: {payload.consecutiveYears}
Quarterly spend: Q1 {fmtMoney(payload.spend.Q1)} • Q2 {fmtMoney(payload.spend.Q2)} • Q3 {fmtMoney(payload.spend.Q3)} • Q4 {fmtMoney(payload.spend.Q4)}
Earnings Summary
Free goods (YTD): {fmtMoney(payload.freeGoodsYTD)}
Growth dividend: {fmtMoney(payload.dividendPayout)} ({payload.dividendExplain})
Royalty: {fmtMoney(payload.royaltyPayout)} ({payload.royaltyExplain})
{payload.notes ? (
Notes
{payload.notes}
) : null}
Quarter gates & free goods
{payload.qRows.map((r) => ( ))}
Quarter Spend Cumulative Required Gate Free goods
{r.q} {fmtMoney(r.spendQ)} {fmtMoney(r.cumulative)} {fmtMoney(r.required)} {r.passed ? "PASS" : "MISS"} {fmtMoney(r.earned)}
Pricing tiers: Silver/Gold/Platinum based on annual spend. “+” levels only apply to dividend rate within fixed spend bands.
); } function ScenarioCard({ scenario, settings, onChange, onDuplicate, onDelete, onRename, onReset, onPrint, }) { const annualTotal = useMemo(() => Object.values(scenario.spend).reduce((s, v) => s + Number(v || 0), 0), [scenario.spend]); const pricingFamily = useMemo(() => pricingTierFromAnnual(annualTotal, settings.mins), [annualTotal, settings.mins]); const dividendLevel = useMemo( () => dividendLevelFromSpend(pricingFamily, annualTotal, settings), [pricingFamily, annualTotal, settings] ); const activation = useMemo( () => activationStatus(scenario.memberType, scenario.enrolledTier, Number(scenario.spend90d || 0), settings), [scenario.memberType, scenario.enrolledTier, scenario.spend90d, settings] ); const freeGoods = useMemo( () => quarterlyFreeGoods(scenario.spend, pricingFamily, settings), [scenario.spend, pricingFamily, settings] ); const dividend = useMemo( () => growthDividend({ annualTotal, baselinePY: Number(scenario.baselinePY || 0), pricingFamily, dividendLevel, settings, }), [annualTotal, scenario.baselinePY, pricingFamily, dividendLevel, settings] ); const royalty = useMemo( () => royaltyPayout({ annualTotal, pricingFamily, consecutiveYears: Number(scenario.consecutiveYears || 0), settings, }), [annualTotal, pricingFamily, scenario.consecutiveYears, settings] ); const { nextTier, gapToNextTier, nextPlusLabel, gapToNextPlus } = useMemo( () => computeGaps(annualTotal, pricingFamily, dividendLevel, settings), [annualTotal, pricingFamily, dividendLevel, settings] ); const qualificationBadge = useMemo(() => { if (pricingFamily === "Not Qualified") return { label: "Below Minimum", variant: "secondary" }; return { label: `Pricing: ${pricingFamily}`, variant: "default" }; }, [pricingFamily]); const dividendBadge = useMemo(() => { if (pricingFamily === "Not Qualified") return { label: "Dividend: N/A", variant: "secondary" }; return { label: `Dividend: ${dividendLevel} (${fmtPct((settings.dividendRate[dividendLevel] ?? 0) * 100)})`, variant: "outline", }; }, [pricingFamily, dividendLevel, settings]); const chartData = useMemo(() => { const qOrder = ["Q1", "Q2", "Q3", "Q4"]; return qOrder.map((q) => { const d = freeGoods.byQuarter[q]; return { quarter: q, spend: d?.spendQ ?? 0, cumulative: d?.cumulative ?? 0, required: d?.required ?? 0, freeGoods: d?.earned ?? 0, }; }); }, [freeGoods]); const setSpend = (q, value) => { const v = Number(value); onChange({ ...scenario, spend: { ...scenario.spend, [q]: Number.isFinite(v) ? v : 0 } }); }; const setField = (key, value) => onChange({ ...scenario, [key]: value }); const riskNotes = useMemo(() => { const notes = []; if (scenario.memberType === "New") { if (activation.disqualified) notes.push("New-member activation: currently below 90% of the 90-day target (disqualified)."); else if (!activation.qualified) notes.push("New-member activation: not yet qualified."); else if (activation.bonus === 0) notes.push("New-member activation: qualified, but below 100% (no $500 bonus)."); } if (pricingFamily !== "Not Qualified") { const missed = Object.entries(freeGoods.byQuarter) .filter(([q, d]) => q !== "Q4" && d && !d.passed) .map(([q]) => q); if (missed.length) notes.push(`Free goods gates missed so far: ${missed.join(", ")}.`); } if (!dividend.eligible && pricingFamily !== "Not Qualified") notes.push(`Dividend not earned: ${dividend.reason}.`); // Plus-band note (important because Platinum+ is a fixed band) if (pricingFamily === "Platinum") { const b = settings.plusBands.PlatinumPlus; if (annualTotal > b.max) { notes.push( `Platinum+ is defined as ${fmtMoney(b.min)}–${fmtMoney(b.max)}. This scenario is above that band, so it uses Platinum (19%) dividend rate.` ); } } return notes; }, [scenario.memberType, activation, pricingFamily, freeGoods, dividend, annualTotal, settings]); const tileClass = `rounded-2xl bg-gradient-to-b ${tierAccent(pricingFamily)} border border-slate-200/60`; return (

{scenario.name}

{qualificationBadge.label} {dividendBadge.label} {scenario.pinned ? ( Pinned ) : null}
Member: {scenario.memberType} • Enrolled tier (90-day target):{" "} {scenario.enrolledTier}
Scenario onDuplicate(scenario)}> Duplicate { onChange({ ...scenario, pinned: !scenario.pinned }); }} > {scenario.pinned ? ( <> Unpin from compare ) : ( <> Pin for compare )} onReset(scenario)}> Reset inputs onPrint(scenario)}> Print / Save PDF { const newName = prompt("Rename scenario", scenario.name); if (newName && newName.trim()) onRename({ ...scenario, name: newName.trim() }); }} > Rename onDelete(scenario)}> Delete
{/* Inputs */}
{scenario.memberType === "New" ? (
setField("spend90d", Number(e.target.value))} min={0} />
) : (
setField("spend90d", Number(e.target.value))} min={0} disabled placeholder="N/A for Existing" />
)}
setField("baselinePY", Number(e.target.value))} min={0} />
setField("consecutiveYears", Number(e.target.value))} min={0} />
{(["Q1", "Q2", "Q3", "Q4"]).map((q) => (
setSpend(q, e.target.value)} min={0} />
))}
setField("notes", e.target.value)} placeholder="Optional (internal only)" />
{/* KPI Tiles */}
Annual qualified spend
{fmtMoney(annualTotal)}
Pricing family: {pricingFamily}
Free goods (YTD earned)
{fmtMoney(freeGoods.totalEarned)}
Rate: {fmtPct(freeGoods.rate * 100)}
Growth dividend (projected)
{fmtMoney(dividend.payout)}
{dividend.eligible ? ( Growth: {fmtMoney(dividend.growth)} • Rate:{" "} {fmtPct(dividend.rate * 100)} ) : ( {dividend.reason} )}
{/* Gap tiles */}
Gap to next pricing tier
{nextTier ? ( <>
{fmtMoney(gapToNextTier)}
Next tier: {nextTier} {fmtMoney(settings.mins[nextTier])}
) : ( <>
Already at top pricing tier
)}
Gap to higher dividend (+)
{nextPlusLabel ? ( <>
{fmtMoney(gapToNextPlus)}
Target: {nextPlusLabel} {nextPlusLabel === "Silver+" ? fmtMoney(settings.plusBands.SilverPlus.min) : nextPlusLabel === "Gold+" ? fmtMoney(settings.plusBands.GoldPlus.min) : fmtMoney(settings.plusBands.PlatinumPlus.min)}
) : ( <>
Already at highest dividend rate for this pricing tier
)}
Royalty milestone
{royalty.milestone ? `${royalty.milestone}-Year` : "—"}
{royalty.eligible ? ( Rate: {fmtPct(royalty.rate * 100)} • Payout:{" "} {fmtMoney(royalty.payout)} ) : ( Not eligible )}
90-day activation
{activation.status}
{activation.na ? (
Shown only for New members.
) : (
Target: {fmtMoney(activation.target)} • Progress: {fmtPct(activation.pct * 100)}
)}
0 ? "bg-emerald-600 hover:bg-emerald-600" : "bg-slate-600 hover:bg-slate-600" } > {activation.na ? "N/A" : activation.bonus > 0 ? `Bonus ${fmtMoney(activation.bonus)}` : activation.disqualified ? "Disqualified" : "Qualified"}
Quarter gates (cumulative)
Free goods are earned on the quarter’s spend only if the cumulative gate is met by end of that quarter.
{(["Q1", "Q2", "Q3", "Q4"]).map((q) => { const d = freeGoods.byQuarter[q]; if (!d) return null; return (
{q} Required {fmtMoney(d.required)}
{d.passed ? "Pass" : "Miss"} Earned {fmtMoney(d.earned)}
); })}
Spend vs gates
(v >= 1000 ? `${Math.round(v / 1000)}k` : v)} /> [fmtMoney(v), n]} />
Free goods earned by quarter
(v >= 1000 ? `${Math.round(v / 1000)}k` : v)} /> [fmtMoney(v), n]} />
{riskNotes.length > 0 && (
Flags / coaching points
    {riskNotes.map((n, idx) => (
  • {n}
  • ))}
)}
); } function SettingsPanel({ settings, setSettings }) { const update = (path, value) => { const next = structuredClone(settings); const [a, b, c] = path; if (c !== undefined) next[a][b][c] = value; else next[a][b] = value; setSettings(next); }; return (
Admin settings (editable)
Tune rates, gates, and + bands during internal testing.
Saved locally
Annual minimums
{(["Silver", "Gold", "Platinum"]).map((k) => (
update(["mins", k], Number(e.target.value))} />
))}
Free goods rate
{(["Silver", "Gold", "Platinum"]).map((k) => (
update(["freeGoodsRate", k], Number(e.target.value))} />
))}
Enter as decimals (0.12 = 12%).
Dividend rates
{(["Silver", "Silver+", "Gold", "Gold+", "Platinum", "Platinum+"]).map((k) => (
update(["dividendRate", k], Number(e.target.value))} />
))}
Enter as decimals (0.18 = 18%).
Quarter gates (cumulative)
{(["Q1", "Q2", "Q3", "Q4"]).map((k) => (
update(["quarterGates", k], Number(e.target.value))} />
))}
0.25 = 25% of annual minimum by end of Q1, etc.
Dividend “+” spend bands
These bands only affect dividend rate — not pricing tier.
Silver+ band
update(["plusBands", "SilverPlus", "min"], Number(e.target.value))} />
update(["plusBands", "SilverPlus", "max"], Number(e.target.value))} />
Gold+ band
update(["plusBands", "GoldPlus", "min"], Number(e.target.value))} />
update(["plusBands", "GoldPlus", "max"], Number(e.target.value))} />
Platinum+ band
update(["plusBands", "PlatinumPlus", "min"], Number(e.target.value))} />
update(["plusBands", "PlatinumPlus", "max"], Number(e.target.value))} />
Activation rules
update(["activation", "targetPct"], Number(e.target.value))} />
update(["activation", "bonusAmount"], Number(e.target.value))} />
update(["activation", "bonusThreshold"], Number(e.target.value))} />
update(["activation", "noBonusFloor"], Number(e.target.value))} />
Example: 0.25 target, 1.0 bonus, 0.9 qualify floor.
); } function computeScenarioMetrics(s, settings) { const annualTotal = Object.values(s.spend).reduce((sum, v) => sum + Number(v || 0), 0); const pricingFamily = pricingTierFromAnnual(annualTotal, settings.mins); const dividendLevel = dividendLevelFromSpend(pricingFamily, annualTotal, settings); const activation = activationStatus(s.memberType, s.enrolledTier, Number(s.spend90d || 0), settings); const freeGoods = quarterlyFreeGoods(s.spend, pricingFamily, settings); const dividend = growthDividend({ annualTotal, baselinePY: Number(s.baselinePY || 0), pricingFamily, dividendLevel, settings, }); const royalty = royaltyPayout({ annualTotal, pricingFamily, consecutiveYears: Number(s.consecutiveYears || 0), settings, }); const gaps = computeGaps(annualTotal, pricingFamily, dividendLevel, settings); return { annualTotal, pricingFamily, dividendLevel, activation, freeGoods, dividend, royalty, gaps }; } function CompareBar({ pinned, settings }) { const rows = useMemo(() => { return pinned.map((s) => { const m = computeScenarioMetrics(s, settings); return { id: s.id, name: s.name, pricing: m.pricingFamily, divLevel: m.dividendLevel, spend: m.annualTotal, freeGoods: m.freeGoods.totalEarned, dividend: m.dividend.payout, activation: m.activation.status, }; }); }, [pinned, settings]); if (pinned.length === 0) return null; return (
Compare mode
Pinned scenarios (up to 4 recommended)
{pinned.length} pinned
{rows.map((r) => ( ))}
Scenario Pricing Dividend Level Annual Spend Free Goods (YTD) Dividend Activation
{r.name} {r.pricing} {r.divLevel} {fmtMoney(r.spend)} {fmtMoney(r.freeGoods)} {fmtMoney(r.dividend)} {r.activation}
); } export default function App() { const [settings, setSettings] = useState(DEFAULT_SETTINGS); const [scenarios, setScenarios] = useState([ makeScenario({ name: "Silver Growth Example", enrolledTier: "Silver", memberType: "New" }), makeScenario({ name: "Gold Growth Example", enrolledTier: "Gold", memberType: "Existing" }), makeScenario({ name: "Platinum Growth Example", enrolledTier: "Platinum", memberType: "Existing" }) ]); const [activeTab, setActiveTab] = useState("scenarios"); // print mode const [printPayload, setPrintPayload] = useState(null); // Load persisted useEffect(() => { const loadedSettings = loadJSON(SETTINGS_KEY, DEFAULT_SETTINGS); // shallow merge with defaults const merged = { ...DEFAULT_SETTINGS, ...loadedSettings, mins: { ...DEFAULT_SETTINGS.mins, ...(loadedSettings?.mins || {}) }, freeGoodsRate: { ...DEFAULT_SETTINGS.freeGoodsRate, ...(loadedSettings?.freeGoodsRate || {}) }, quarterGates: { ...DEFAULT_SETTINGS.quarterGates, ...(loadedSettings?.quarterGates || {}) }, activation: { ...DEFAULT_SETTINGS.activation, ...(loadedSettings?.activation || {}) }, dividendRate: { ...DEFAULT_SETTINGS.dividendRate, ...(loadedSettings?.dividendRate || {}) }, plusBands: { ...DEFAULT_SETTINGS.plusBands, ...(loadedSettings?.plusBands || {}) }, royalty: { ...DEFAULT_SETTINGS.royalty, ...(loadedSettings?.royalty || {}) }, pricingReview: { ...DEFAULT_SETTINGS.pricingReview, ...(loadedSettings?.pricingReview || {}) }, }; setSettings(merged); const loadedScenarios = loadJSON(STORAGE_KEY, null); if (Array.isArray(loadedScenarios) && loadedScenarios.length) { // backfill pinned flag setScenarios(loadedScenarios.map((s) => ({ ...makeScenario({ ...s, id: s.id ?? uuid() }), ...s }))); } }, []); useEffect(() => { saveJSON(STORAGE_KEY, scenarios); }, [scenarios]); useEffect(() => { saveJSON(SETTINGS_KEY, settings); }, [settings]); const pinned = useMemo(() => scenarios.filter((s) => s.pinned), [scenarios]); const addScenario = () => { const n = scenarios.length + 1; setScenarios((prev) => [...prev, makeScenario({ name: `Scenario ${n}` })]); }; const duplicateScenario = (s) => { const copy = makeScenario({ name: `${s.name} (copy)`, memberType: s.memberType, enrolledTier: s.enrolledTier, spend: { ...s.spend }, spend90d: s.spend90d, baselinePY: s.baselinePY, consecutiveYears: s.consecutiveYears, notes: s.notes, pinned: false, }); setScenarios((prev) => [...prev, copy]); }; const deleteScenario = (s) => setScenarios((prev) => prev.filter((x) => x.id !== s.id)); const resetScenario = (s) => { setScenarios((prev) => prev.map((x) => (x.id === s.id ? makeScenario({ id: x.id, name: x.name }) : x))); }; const updateScenario = (next) => setScenarios((prev) => prev.map((x) => (x.id === next.id ? next : x))); const renameScenario = (next) => updateScenario(next); function loadExamples() { setScenarios([ makeScenario({ name: "Silver Growth Example", enrolledTier: "Silver", memberType: "New" }), makeScenario({ name: "Gold Growth Example", enrolledTier: "Gold", memberType: "Existing" }), makeScenario({ name: "Platinum Growth Example", enrolledTier: "Platinum", memberType: "Existing" }), ]); } const clearAll = () => { if (!confirm("Reset ALL scenarios to blank inputs?")) return; setScenarios((prev) => prev.map((s) => makeScenario({ id: s.id, name: s.name }))); }; const headerStats = useMemo(() => { const totals = scenarios.map((s) => Object.values(s.spend).reduce((sum, v) => sum + Number(v || 0), 0)); const sum = totals.reduce((a, b) => a + b, 0); const avg = totals.length ? sum / totals.length : 0; const min = totals.length ? Math.min(...totals) : 0; const max = totals.length ? Math.max(...totals) : 0; return { avg, min, max }; }, [scenarios]); const printScenario = (scenario) => { const m = computeScenarioMetrics(scenario, settings); const qOrder = ["Q1", "Q2", "Q3", "Q4"]; const qRows = qOrder.map((q) => ({ q, ...m.freeGoods.byQuarter[q], })); const tiles = [ { label: "Annual Spend", value: fmtMoney(m.annualTotal), sub: `Pricing: ${m.pricingFamily}` }, { label: "Dividend Level", value: m.pricingFamily === "Not Qualified" ? "N/A" : m.dividendLevel, sub: m.pricingFamily === "Not Qualified" ? "Below minimum" : `Rate ${fmtPct((settings.dividendRate[m.dividendLevel] || 0) * 100)}`, }, { label: "Free Goods (YTD)", value: fmtMoney(m.freeGoods.totalEarned), sub: `Rate ${fmtPct(m.freeGoods.rate * 100)}` }, ]; setPrintPayload({ name: scenario.name, memberType: scenario.memberType, enrolledTier: scenario.enrolledTier, spend90d: Number(scenario.spend90d || 0), baselinePY: Number(scenario.baselinePY || 0), consecutiveYears: Number(scenario.consecutiveYears || 0), spend: scenario.spend, freeGoodsYTD: m.freeGoods.totalEarned, dividendPayout: m.dividend.payout, dividendExplain: m.dividend.eligible ? `Growth ${fmtMoney(m.dividend.growth)} @ ${fmtPct(m.dividend.rate * 100)}` : m.dividend.reason, royaltyPayout: m.royalty.payout, royaltyExplain: m.royalty.eligible ? `${m.royalty.milestone}-year @ ${fmtPct(m.royalty.rate * 100)}` : "Not eligible", notes: scenario.notes, qRows, tiles, }); // Delay to allow payload to render, then print setTimeout(() => window.print(), 50); }; return (
{/* Print Sheet */}
{printPayload ? : null}
{/* App UI (hidden in print) */}
{/* Header */}

Accelerate 3.0 — Scenario Calculator

Team Tool

Interactive multi-scenario testing. Pricing tiers are Silver/Gold/Platinum. “+” levels only increase dividend rate.

Dividend + bands: Silver+ {fmtMoney(settings.plusBands.SilverPlus.min)}–{fmtMoney(settings.plusBands.SilverPlus.max)} • Gold+{" "} {fmtMoney(settings.plusBands.GoldPlus.min)}–{fmtMoney(settings.plusBands.GoldPlus.max)} • Platinum+{" "} {fmtMoney(settings.plusBands.PlatinumPlus.min)}–{fmtMoney(settings.plusBands.PlatinumPlus.max)}
{/* Overview */}
Scenarios
{scenarios.length}
Avg annual spend
{fmtMoney(headerStats.avg)}
Min / Max spend
{fmtMoney(headerStats.min)} / {fmtMoney(headerStats.max)}
{/* Compare Bar */}
Scenarios Admin Settings
{scenarios.map((s) => ( ))}
Print/Save PDF: open a scenario menu → “Print / Save PDF”. In the print dialog, choose “Save as PDF”.
{/* Print CSS */}
); }