/** * EstimateWizardAdmin — internal admin controls + analytics (spec §14, §16, §18). * * Tenant-controls (no hard-coded pricing/copy): feature kill-switch, AI toggle, * conservative mode, per-package price + base discount + service life, and report * branding. Persists as overrides via saveConfigOverrides(). Also shows a funnel * summary built from completed-session submissions (spec §16). */ import React, { useMemo, useState } from 'react'; import { toast } from 'sonner'; import { Save, RotateCcw, Power, Sparkles, ShieldCheck, BarChart3, Package, History } from 'lucide-react'; import { getTenantConfig, loadConfigOverrides, publishConfigOverrides, resetConfigOverrides, } from './data/tenantConfig'; import { loadSubmissions } from './api/crmHandoff'; const PKG_LABEL = { GOOD: 'Good', BETTER: 'Better', BEST: 'Best' }; const Toggle = ({ label, desc, checked, onChange, icon: Icon }) => ( ); const numInput = 'w-full rounded-lg border border-zinc-200 dark:border-white/10 bg-white dark:bg-zinc-900 px-2.5 py-1.5 text-sm text-zinc-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-amber-500'; const textInput = numInput; const areaInput = numInput + ' min-h-[64px] resize-y leading-relaxed'; const EstimateWizardAdmin = () => { const initial = useMemo(() => getTenantConfig(), []); const [cfg, setCfg] = useState(initial); const [versionInfo, setVersionInfo] = useState(() => { const o = loadConfigOverrides(); return { version: initial.version, versionedAt: o.versionedAt || null, history: o.versionHistory || [] }; }); const submissions = useMemo(() => loadSubmissions(), []); const analytics = useMemo(() => { const total = submissions.length; const dist = (key) => submissions.reduce((m, s) => { const k = s[key]; if (k) m[k] = (m[k] || 0) + 1; return m; }, {}); const booked = submissions.filter((s) => s.report?.appointmentBooked).length; const addonAttach = total ? (submissions.reduce((n, s) => n + (s.selectedAddons?.length || 0), 0) / total) : 0; return { total, recommended: dist('recommendedPackage'), selected: dist('selectedPackage'), booked, addonAttach }; }, [submissions]); const setFlag = (k, v) => setCfg((c) => ({ ...c, [k]: v })); const setPkg = (type, field, value) => setCfg((c) => ({ ...c, packages: c.packages.map((p) => (p.type === type ? { ...p, [field]: value } : p)), })); // Included scope is edited as one item per line. const setScope = (type, text) => setPkg(type, 'scope', text.split('\n').map((s) => s.trim()).filter(Boolean)); const setDiscount = (type, value) => setCfg((c) => ({ ...c, discountScenarios: { ...c.discountScenarios, [type]: { ...c.discountScenarios[type], base: value } }, })); const setBranding = (field, value) => setCfg((c) => ({ ...c, branding: { ...c.branding, [field]: value } })); const handleSave = () => { const prev = loadConfigOverrides(); const overrides = { ...prev, featureEnabled: cfg.featureEnabled, aiExplanationsEnabled: cfg.aiExplanationsEnabled, conservativeMode: cfg.conservativeMode, branding: cfg.branding, discountScenarios: cfg.discountScenarios, // Full Good/Better/Best content is tenant-controlled (spec §4/§14). packages: cfg.packages.map((p) => ({ type: p.type, name: p.name, category: p.category, warrantyLabel: p.warrantyLabel, hailResistance: p.hailResistance, position: p.position, fit: p.fit, scope: p.scope, pricePerSquare: p.pricePerSquare, serviceLifeYears: p.serviceLifeYears, })), }; const saved = publishConfigOverrides(overrides); // bumps version + history setVersionInfo({ version: saved.version, versionedAt: saved.versionedAt, history: saved.versionHistory }); setCfg((c) => ({ ...c, version: saved.version })); toast.success(`Published ${saved.version}`); }; const handleReset = () => { resetConfigOverrides(); const fresh = getTenantConfig(); setCfg(fresh); setVersionInfo({ version: fresh.version, versionedAt: null, history: [] }); toast.message('Reset to defaults'); }; return (
Tenant controls for package content, pricing, discounts, AI, branding, and analytics.
Every customer-facing field is editable here — nothing is hard-coded. Saving publishes a new version.
Customer copy always states discounts are illustrative — "ask your carrier to confirm."
No completed estimates yet. Finish a wizard run to populate metrics.
) : (No published changes yet — currently on defaults ({versionInfo.version}). Each save publishes a new version that reports are stamped with.
) : (