| import React, { useState, useEffect } from 'react'; |
| import { motion } from 'framer-motion'; |
|
|
| const GrowthAnalyticsAdmin = ({ onBack }) => { |
| const [robustness, setRobustness] = useState(null); |
| const [bumpRate, setBumpRate] = useState(null); |
|
|
| useEffect(() => { |
| fetch('http://localhost:8000/api/v1/metrics/robustness') |
| .then(res => res.json()) |
| .then(data => setRobustness(data)) |
| .catch(err => console.error(err)); |
|
|
| fetch('http://localhost:8000/api/v1/metrics/bump-rate') |
| .then(res => res.json()) |
| .then(data => setBumpRate(data)) |
| .catch(err => console.error(err)); |
| }, []); |
|
|
| return ( |
| <> |
| |
| {/* BEGIN: SidebarNavigation */} |
| <aside className="w-64 border-r border-obsidian-border bg-obsidian-900 flex flex-col flex-shrink-0 z-20"> |
| {/* Brand Logo */} |
| <div className="h-16 flex items-center px-6 border-b border-obsidian-border"> |
| <div className="flex items-center gap-2 text-zinc-100 font-semibold tracking-wide"> |
| <svg className="w-5 h-5 text-accent-500" fill="none" stroke="currentColor" strokeWidth="2" viewbox="0 0 24 24"> |
| <path d="M13 10V3L4 14h7v7l9-11h-7z" strokeLinecap="round" strokeLinejoin="round"></path> |
| </svg> |
| HyperFlow |
| </div> |
| </div> |
| {/* Navigation Links */} |
| <nav className="flex-1 overflow-y-auto py-4 px-3 space-y-1"> |
| <a className="flex items-center gap-3 px-3 py-2 rounded bg-obsidian-800 border border-obsidian-border text-zinc-100 transition-colors group cursor-pointer" onClick={onBack} href="#"> |
| <svg className="w-4 h-4 text-accent-400" fill="none" stroke="currentColor" viewbox="0 0 24 24"><path d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2"></path></svg> |
| <span className="text-sm font-medium">Dashboard</span> |
| </a> |
| <a className="flex items-center gap-3 px-3 py-2 rounded hover:bg-obsidian-700 text-zinc-400 hover:text-zinc-100 transition-colors group" href="#"> |
| <svg className="w-4 h-4 text-zinc-500 group-hover:text-zinc-300" fill="none" stroke="currentColor" viewbox="0 0 24 24"><path d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2"></path></svg> |
| <span className="text-sm font-medium">Analytics</span> |
| </a> |
| <a className="flex items-center gap-3 px-3 py-2 rounded hover:bg-obsidian-700 text-zinc-400 hover:text-zinc-100 transition-colors group" href="#"> |
| <svg className="w-4 h-4 text-zinc-500 group-hover:text-zinc-300" fill="none" stroke="currentColor" viewbox="0 0 24 24"><path d="M11 3.055A9.001 9.001 0 1020.945 13H11V3.055z" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2"></path><path d="M20.488 9H15V3.512A9.025 9.025 0 0120.488 9z" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2"></path></svg> |
| <span className="text-sm font-medium">Campaigns</span> |
| </a> |
| <a className="flex items-center gap-3 px-3 py-2 rounded hover:bg-obsidian-700 text-zinc-400 hover:text-zinc-100 transition-colors group" href="#"> |
| <svg className="w-4 h-4 text-zinc-500 group-hover:text-zinc-300" fill="none" stroke="currentColor" viewbox="0 0 24 24"><path d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2"></path></svg> |
| <span className="text-sm font-medium">Users</span> |
| </a> |
| </nav> |
| </aside> |
| {/* END: SidebarNavigation */} |
| {/* BEGIN: MainContentArea */} |
| <motion.main |
| initial={{ opacity: 0, x: 20 }} |
| animate={{ opacity: 1, x: 0 }} |
| transition={{ duration: 0.5 }} |
| className="flex-1 flex flex-col min-w-0 bg-obsidian-900 z-10 relative" |
| > |
| {/* BEGIN: TopHeader */} |
| <header className="h-16 border-b border-obsidian-border bg-obsidian-900 flex items-center justify-between px-6 flex-shrink-0 sticky top-0 z-20"> |
| {/* Search */} |
| <div className="flex-1 max-w-md relative"> |
| <svg className="w-4 h-4 text-zinc-500 absolute left-3 top-1/2 -translate-y-1/2" fill="none" stroke="currentColor" viewbox="0 0 24 24"><path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2"></path></svg> |
| <input className="w-full bg-obsidian-800 border border-obsidian-border rounded pl-9 pr-4 py-1.5 text-sm text-zinc-200 placeholder-zinc-500 focus:outline-none focus:border-zinc-500 transition-colors" placeholder="Search analytics..." type="text"/> |
| </div> |
| {/* Right Actions */} |
| <div className="flex items-center gap-4 ml-4"> |
| <button className="text-zinc-400 hover:text-zinc-200 transition-colors"> |
| <svg className="w-5 h-5" fill="none" stroke="currentColor" viewbox="0 0 24 24"><path d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2"></path></svg> |
| </button> |
| {/* User Avatar Container */} |
| <div className="w-8 h-8 rounded-full border border-obsidian-border overflow-hidden bg-obsidian-800"> |
| <img alt="User Profile" className="w-full h-full object-cover" src="https://lh3.googleusercontent.com/aida/AP1WRLs8ICeO9hIXxS0I65mspRo4MYHMSNpqxY4xzosQR8V187PTBxfMXWhQa9C6s2Hobl93IUmx58bbg79iSQRugsFpqkAvhTlOuNO0UIE9mft1eW7JHY2_V3px21XpRiqsmH0YVOjihO5lzdSw9Fud6PyR-ryqISNsoTaOfZl89Q1z5B4fOmb82WhxN6gTUpKfn2x7n8kHbtYg9B0yYYCddpBdFTl-BwrYxWPBFsIg69BGadxTVg6v46JIiunZ"/> |
| </div> |
| </div> |
| </header> |
| {/* END: TopHeader */} |
| {/* BEGIN: DashboardContent */} |
| <div className="flex-1 overflow-y-auto p-6 lg:p-8"> |
| <div className="max-w-7xl mx-auto space-y-6"> |
| {/* Page Title & Actions */} |
| <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4"> |
| <h1 className="text-2xl font-semibold text-zinc-100 tracking-tight">Growth & Analytics</h1> |
| <div className="flex items-center gap-2"> |
| <select className="bg-obsidian-800 border border-obsidian-border rounded px-3 py-1.5 text-sm text-zinc-300 focus:outline-none focus:border-zinc-500"> |
| <option>Last 30 Days</option> |
| <option>Last Quarter</option> |
| <option>Year to Date</option> |
| </select> |
| <button className="bg-zinc-100 hover:bg-white text-zinc-900 border border-transparent rounded px-4 py-1.5 text-sm font-medium transition-colors"> |
| Export Report |
| </button> |
| </div> |
| </div> |
| {/* BEGIN: KPIGrid */} |
| <section className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4" data-purpose="kpi-metrics"> |
| {/* KPI 1: Robustness Status */} |
| <div className="bg-obsidian-800 border border-obsidian-border rounded-lg p-5 flex flex-col justify-between"> |
| <div className="text-sm text-zinc-400 font-medium mb-1">ML Robustness Status</div> |
| <div className="flex items-end justify-between"> |
| <div className="text-3xl font-semibold text-zinc-100 uppercase">{robustness ? robustness.status : "Loading"}</div> |
| <div className="text-sm text-emerald-400 flex items-center gap-1"> |
| <svg className="w-3 h-3" fill="none" stroke="currentColor" viewbox="0 0 24 24"><path d="M5 10l7-7m0 0l7 7m-7-7v18" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2"></path></svg> |
| Active |
| </div> |
| </div> |
| </div> |
| {/* KPI 2: Jitter Suppression */} |
| <div className="bg-obsidian-800 border border-obsidian-border rounded-lg p-5 flex flex-col justify-between"> |
| <div className="text-sm text-zinc-400 font-medium mb-1">ETA Jitter Suppression</div> |
| <div className="flex items-end justify-between"> |
| <div className="text-3xl font-semibold text-zinc-100">{bumpRate ? `${bumpRate.jitter_suppression_pct}%` : "Loading"}</div> |
| <div className="text-sm text-emerald-400 flex items-center gap-1"> |
| Gated Bumps: {bumpRate ? bumpRate.gated_smoother_bumps : "--"} |
| </div> |
| </div> |
| </div> |
| {/* KPI 3: Total Clipped Obs */} |
| <div className="bg-obsidian-800 border border-obsidian-border rounded-lg p-5 flex flex-col justify-between"> |
| <div className="text-sm text-zinc-400 font-medium mb-1">Clipped Outliers Today</div> |
| <div className="flex items-end justify-between"> |
| <div className="text-3xl font-semibold text-zinc-100">{robustness?.clipping_guard?.total_clipped_observations_today ?? "Loading"}</div> |
| <div className="text-sm text-zinc-500 flex items-center gap-1"> |
| Via Safing Guard |
| </div> |
| </div> |
| </div> |
| {/* KPI 4: Feature Drift Max */} |
| <div className="bg-obsidian-800 border border-obsidian-border rounded-lg p-5 flex flex-col justify-between"> |
| <div className="text-sm text-zinc-400 font-medium mb-1">Avg PSI Drift</div> |
| <div className="flex items-end justify-between"> |
| <div className="text-3xl font-semibold text-zinc-100"> |
| {robustness ? |
| (Object.values(robustness.features_drift).reduce((a, b) => a + b.psi, 0) / 3).toFixed(4) |
| : "Loading"} |
| </div> |
| <div className="text-sm text-emerald-400 flex items-center gap-1"> |
| Stable |
| </div> |
| </div> |
| </div> |
| </section> |
| {/* END: KPIGrid */} |
| {/* BEGIN: MainChartsSection */} |
| <section className="grid grid-cols-1 lg:grid-cols-3 gap-6"> |
| {/* User Growth Chart (2 columns wide) */} |
| <div className="lg:col-span-2 bg-obsidian-800 border border-obsidian-border rounded-lg p-5 flex flex-col min-h-[300px]"> |
| <div className="flex items-center justify-between mb-4"> |
| <h3 className="text-base font-medium text-zinc-200">User Growth Trends</h3> |
| <div className="flex items-center gap-3 text-xs text-zinc-400"> |
| <div className="flex items-center gap-1"><span className="w-2 h-2 rounded-full bg-accent-500"></span> New Signups</div> |
| <div className="flex items-center gap-1"><span className="w-2 h-2 rounded-full bg-zinc-600"></span> Churn</div> |
| </div> |
| </div> |
| {/* Chart Container (Placeholder for Canvas/SVG) */} |
| <div className="flex-1 w-full relative" id="growth-chart-container"> |
| <canvas className="w-full h-full" id="growthChart"></canvas> |
| </div> |
| </div> |
| {/* Retention Metrics (1 column wide) */} |
| <div className="bg-obsidian-800 border border-obsidian-border rounded-lg p-5 flex flex-col"> |
| <h3 className="text-base font-medium text-zinc-200 mb-4">Top Conversion Sources</h3> |
| <div className="flex-1 space-y-4"> |
| {/* Source Item */} |
| <div className="space-y-1"> |
| <div className="flex justify-between text-sm"> |
| <span className="text-zinc-300">Organic Search</span> |
| <span className="text-zinc-100 font-medium">45%</span> |
| </div> |
| <div className="w-full h-1.5 bg-obsidian-900 rounded overflow-hidden border border-obsidian-border"> |
| <div className="h-full bg-accent-500 w-[45%]"></div> |
| </div> |
| </div> |
| {/* Source Item */} |
| <div className="space-y-1"> |
| <div className="flex justify-between text-sm"> |
| <span className="text-zinc-300">Paid Social (Q3)</span> |
| <span className="text-zinc-100 font-medium">28%</span> |
| </div> |
| <div className="w-full h-1.5 bg-obsidian-900 rounded overflow-hidden border border-obsidian-border"> |
| <div className="h-full bg-accent-400 w-[28%]"></div> |
| </div> |
| </div> |
| {/* Source Item */} |
| <div className="space-y-1"> |
| <div className="flex justify-between text-sm"> |
| <span className="text-zinc-300">Referrals</span> |
| <span className="text-zinc-100 font-medium">15%</span> |
| </div> |
| <div className="w-full h-1.5 bg-obsidian-900 rounded overflow-hidden border border-obsidian-border"> |
| <div className="h-full bg-zinc-400 w-[15%]"></div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </section> |
| {/* END: MainChartsSection */} |
| {/* BEGIN: CampaignTable */} |
| <section className="bg-obsidian-800 border border-obsidian-border rounded-lg overflow-hidden"> |
| <div className="px-5 py-4 border-b border-obsidian-border flex items-center justify-between bg-obsidian-900/50"> |
| <h3 className="text-base font-medium text-zinc-200">Active Marketing Campaigns</h3> |
| <button className="text-sm text-accent-400 hover:text-accent-300 font-medium">View All</button> |
| </div> |
| <div className="overflow-x-auto"> |
| <table className="w-full text-left border-collapse whitespace-nowrap"> |
| <thead> |
| <tr className="bg-obsidian-900/20"> |
| <th className="px-5 py-3 text-xs font-medium text-zinc-500 uppercase tracking-wider border-b border-obsidian-border">Campaign Name</th> |
| <th className="px-5 py-3 text-xs font-medium text-zinc-500 uppercase tracking-wider border-b border-obsidian-border">Status</th> |
| <th className="px-5 py-3 text-xs font-medium text-zinc-500 uppercase tracking-wider border-b border-obsidian-border">Spend</th> |
| <th className="px-5 py-3 text-xs font-medium text-zinc-500 uppercase tracking-wider border-b border-obsidian-border">Conversion (CPA)</th> |
| <th className="px-5 py-3 text-xs font-medium text-zinc-500 uppercase tracking-wider border-b border-obsidian-border text-right">ROI</th> |
| </tr> |
| </thead> |
| <tbody className="divide-y divide-obsidian-border"> |
| {/* Row 1 */} |
| <tr className="hover:bg-obsidian-700/30 transition-colors"> |
| <td className="px-5 py-4 text-sm text-zinc-200 font-medium">Q3 Retargeting Alpha</td> |
| <td className="px-5 py-4 text-sm"> |
| <span className="inline-flex items-center gap-1.5 px-2 py-0.5 rounded text-xs font-medium bg-emerald-500/10 text-emerald-400 border border-emerald-500/20"> |
| <span className="w-1.5 h-1.5 rounded-full bg-emerald-400"></span> Active |
| </span> |
| </td> |
| <td className="px-5 py-4 text-sm text-zinc-400">$12,450</td> |
| <td className="px-5 py-4 text-sm text-zinc-400">$42.50</td> |
| <td className="px-5 py-4 text-sm text-emerald-400 text-right">+245%</td> |
| </tr> |
| {/* Row 2 */} |
| <tr className="hover:bg-obsidian-700/30 transition-colors"> |
| <td className="px-5 py-4 text-sm text-zinc-200 font-medium">Influencer Batch #4</td> |
| <td className="px-5 py-4 text-sm"> |
| <span className="inline-flex items-center gap-1.5 px-2 py-0.5 rounded text-xs font-medium bg-emerald-500/10 text-emerald-400 border border-emerald-500/20"> |
| <span className="w-1.5 h-1.5 rounded-full bg-emerald-400"></span> Active |
| </span> |
| </td> |
| <td className="px-5 py-4 text-sm text-zinc-400">$8,200</td> |
| <td className="px-5 py-4 text-sm text-zinc-400">$38.10</td> |
| <td className="px-5 py-4 text-sm text-emerald-400 text-right">+180%</td> |
| </tr> |
| {/* Row 3 */} |
| <tr className="hover:bg-obsidian-700/30 transition-colors"> |
| <td className="px-5 py-4 text-sm text-zinc-200 font-medium">Cold Outreach UK</td> |
| <td className="px-5 py-4 text-sm"> |
| <span className="inline-flex items-center gap-1.5 px-2 py-0.5 rounded text-xs font-medium bg-zinc-500/10 text-zinc-400 border border-zinc-500/20"> |
| <span className="w-1.5 h-1.5 rounded-full bg-zinc-500"></span> Paused |
| </span> |
| </td> |
| <td className="px-5 py-4 text-sm text-zinc-400">$3,100</td> |
| <td className="px-5 py-4 text-sm text-zinc-400">$85.00</td> |
| <td className="px-5 py-4 text-sm text-rose-400 text-right">-12%</td> |
| </tr> |
| </tbody> |
| </table> |
| </div> |
| </section> |
| {/* END: CampaignTable */} |
| </div> |
| </div> |
| {/* END: DashboardContent */} |
| </motion.main> |
| {/* END: MainContentArea */} |
| |
| |
| </> |
| ); |
| }; |
|
|
| export default GrowthAnalyticsAdmin; |
|
|