mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 13:35:24 +02:00
feat: replace old detail view entirely with premium dashboard and add safe metric fallbacks
This commit is contained in:
+253
-609
@@ -474,79 +474,99 @@ const ExperimentDetailView: React.FC<{
|
||||
);
|
||||
}
|
||||
|
||||
if (experiment.verdict || didResults) {
|
||||
const prob = Math.round((experiment.verdict_probability || 0) * 100);
|
||||
const radius = 60;
|
||||
const circumference = 2 * Math.PI * radius;
|
||||
const offset = circumference - (prob / 100) * circumference;
|
||||
const prob = Math.round((experiment.verdict_probability || 0) * 100);
|
||||
const radius = 60;
|
||||
const circumference = 2 * Math.PI * radius;
|
||||
const offset = circumference - (prob / 100) * circumference;
|
||||
|
||||
return (
|
||||
<div className="bg-[#0b1120] text-slate-200 min-h-screen -mx-4 md:-mx-6 -mt-4 md:-mt-6 p-6 md:p-8 font-sans">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col md:flex-row items-start justify-between gap-4 mb-8">
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<span className="bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 text-[10px] font-bold uppercase tracking-wider px-2.5 py-1 rounded">
|
||||
EXPERIMENT RESULT
|
||||
</span>
|
||||
<span className="text-slate-400 text-sm font-medium">ASIN: {experiment.asins?.[0] || 'Multiple'}</span>
|
||||
</div>
|
||||
<h1 className="text-3xl md:text-3xl lg:text-4xl font-extrabold text-white mb-3 tracking-tight">
|
||||
{experiment.name}
|
||||
</h1>
|
||||
<div className="flex flex-wrap items-center gap-4 text-sm text-slate-400 font-medium">
|
||||
<div className="flex items-center gap-1.5 whitespace-nowrap">
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg>
|
||||
<span>Test: {new Date(experiment.start_date).toLocaleDateString('en-GB', { month: 'short', day: '2-digit', year: 'numeric' })} - {experiment.end_date ? new Date(experiment.end_date).toLocaleDateString('en-GB', { month: 'short', day: '2-digit', year: 'numeric' }) : 'Ongoing'}</span>
|
||||
</div>
|
||||
<div className="w-1 h-1 rounded-full bg-slate-600 hidden md:block" />
|
||||
<div className="flex items-center gap-1.5 text-indigo-400 whitespace-nowrap">
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /></svg>
|
||||
<span className="uppercase tracking-widest text-[11px] font-bold">PRIMARY GOAL: {METRIC_LABELS[experiment.primary_metric] || experiment.primary_metric}</span>
|
||||
</div>
|
||||
</div>
|
||||
return (
|
||||
<div className="bg-[#0b1120] text-slate-200 min-h-screen -mx-4 md:-mx-6 -mt-4 md:-mt-6 p-6 md:p-8 font-sans">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col xl:flex-row items-start justify-between gap-4 mb-8">
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<span className="bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 text-[10px] font-bold uppercase tracking-wider px-2.5 py-1 rounded">
|
||||
EXPERIMENT RESULT
|
||||
</span>
|
||||
<span className="text-slate-400 text-sm font-medium">ASIN: {experiment.asins?.[0] || 'Multiple'}</span>
|
||||
<StatusBadge status={experiment.status} />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button onClick={onBack} className="p-2.5 px-4 bg-transparent hover:bg-white/5 border border-white/10 rounded-lg transition-colors text-slate-300 font-semibold text-sm">
|
||||
Back
|
||||
</button>
|
||||
<button className="p-2.5 bg-transparent hover:bg-white/5 border border-white/10 rounded-lg transition-colors text-slate-300">
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /></svg>
|
||||
</button>
|
||||
<button className="p-2.5 bg-transparent hover:bg-white/5 border border-white/10 rounded-lg transition-colors text-slate-300">
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z" /></svg>
|
||||
</button>
|
||||
<h1 className="text-3xl md:text-3xl lg:text-4xl font-extrabold text-white mb-3 tracking-tight">
|
||||
{experiment.name}
|
||||
</h1>
|
||||
<div className="flex flex-wrap items-center gap-4 text-sm text-slate-400 font-medium">
|
||||
<div className="flex items-center gap-1.5 whitespace-nowrap">
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg>
|
||||
<span>Test: {new Date(experiment.start_date).toLocaleDateString('en-GB', { month: 'short', day: '2-digit', year: 'numeric' })} - {experiment.end_date ? new Date(experiment.end_date).toLocaleDateString('en-GB', { month: 'short', day: '2-digit', year: 'numeric' }) : 'Ongoing'}</span>
|
||||
</div>
|
||||
<div className="w-1 h-1 rounded-full bg-slate-600 hidden md:block" />
|
||||
<div className="flex items-center gap-1.5 text-indigo-400 whitespace-nowrap">
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /></svg>
|
||||
<span className="uppercase tracking-widest text-[11px] font-bold">PRIMARY GOAL: {METRIC_LABELS[experiment.primary_metric] || experiment.primary_metric}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{experiment.status === 'planned' && (
|
||||
<button onClick={() => handleUpdateStatus('active')} className="px-4 py-2.5 bg-emerald-600/20 hover:bg-emerald-600/30 border border-emerald-500/30 rounded-lg transition-colors text-emerald-400 font-semibold text-sm">
|
||||
Start
|
||||
</button>
|
||||
)}
|
||||
{experiment.status === 'active' && (
|
||||
<button onClick={() => handleUpdateStatus('completed')} className="px-4 py-2.5 bg-indigo-600/20 hover:bg-indigo-600/30 border border-indigo-500/30 rounded-lg transition-colors text-indigo-400 font-semibold text-sm">
|
||||
Complete
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={handleRunAnalysis}
|
||||
disabled={analyzing || salesData.length === 0}
|
||||
className="px-4 py-2.5 bg-indigo-600 hover:bg-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed rounded-lg transition-colors text-white font-semibold text-sm flex items-center gap-2 shadow-[0_0_15px_rgba(79,70,229,0.3)]"
|
||||
>
|
||||
{analyzing ? (
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-2 border-white border-t-transparent" />
|
||||
) : (
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
|
||||
)}
|
||||
Run Analysis
|
||||
</button>
|
||||
<button onClick={onEdit} className="p-2.5 bg-transparent hover:bg-white/5 border border-white/10 rounded-lg transition-colors text-slate-300">
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" /></svg>
|
||||
</button>
|
||||
<button onClick={handleDelete} className="p-2.5 bg-transparent hover:bg-red-500/10 border border-white/10 hover:border-red-500/30 rounded-lg transition-colors text-slate-300 hover:text-red-400">
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>
|
||||
</button>
|
||||
<button onClick={onBack} className="p-2.5 px-4 bg-transparent hover:bg-white/5 border border-white/10 rounded-lg transition-colors text-slate-300 font-semibold text-sm ml-2">
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Grid */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-8">
|
||||
{/* Left Column (Metrics + Content) */}
|
||||
<div className="lg:col-span-2 space-y-8">
|
||||
{/* Main Grid */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-8">
|
||||
{/* Left Column (Metrics + Content) */}
|
||||
<div className="lg:col-span-2 space-y-8">
|
||||
|
||||
{/* Key Performance Metrics */}
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<svg className="w-5 h-5 text-indigo-400" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} 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" /></svg>
|
||||
<h2 className="text-xl font-bold text-white">Key Performance Metrics</h2>
|
||||
</div>
|
||||
<div className="bg-[#0b1120]">
|
||||
<table className="w-full text-left border-collapse">
|
||||
<thead>
|
||||
<tr className="border-b border-white/10">
|
||||
<th className="py-3 text-[10px] uppercase tracking-widest text-[#64748b] font-black w-1/3">KPI Metric</th>
|
||||
<th className="py-3 text-[10px] uppercase tracking-widest text-[#64748b] font-black">Control (A)</th>
|
||||
<th className="py-3 text-[10px] uppercase tracking-widest text-[#64748b] font-black">Treatment (B)</th>
|
||||
<th className="py-3 text-[10px] uppercase tracking-widest text-[#64748b] font-black">Impact Delta</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-white/5">
|
||||
{/* Render specific metrics: Units, Revenue, CVR, BSR/ACOS (if available in didResults) */}
|
||||
{['units', 'revenue', 'cvr', 'acos', 'sessions', 'roas', 'ctr'].map(metric => {
|
||||
const res = didResults?.metrics?.[metric];
|
||||
if (!res) return null;
|
||||
const isPositiveGood = !['acos', 'bsr'].includes(metric);
|
||||
const isGood = isPositiveGood ? res.lift_percent >= 0 : res.lift_percent <= 0;
|
||||
{/* Key Performance Metrics */}
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<svg className="w-5 h-5 text-indigo-400" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} 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" /></svg>
|
||||
<h2 className="text-xl font-bold text-white">Key Performance Metrics</h2>
|
||||
</div>
|
||||
<div className="bg-[#0b1120]">
|
||||
<table className="w-full text-left border-collapse">
|
||||
<thead>
|
||||
<tr className="border-b border-white/10">
|
||||
<th className="py-3 text-[10px] uppercase tracking-widest text-[#64748b] font-black w-1/3">KPI Metric</th>
|
||||
<th className="py-3 text-[10px] uppercase tracking-widest text-[#64748b] font-black">Control (A)</th>
|
||||
<th className="py-3 text-[10px] uppercase tracking-widest text-[#64748b] font-black">Treatment (B)</th>
|
||||
<th className="py-3 text-[10px] uppercase tracking-widest text-[#64748b] font-black">Impact Delta</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-white/5">
|
||||
{/* Render specific metrics: Units, Revenue, CVR, BSR/ACOS */}
|
||||
{['units', 'revenue', 'cvr', 'acos', 'sessions', 'roas', 'ctr'].map(metric => {
|
||||
const res = didResults?.metrics?.[metric];
|
||||
if (!res) {
|
||||
return (
|
||||
<tr key={metric} className="hover:bg-white/[0.02] transition-colors">
|
||||
<td className="py-4 text-[13px] font-bold text-slate-200">
|
||||
@@ -555,563 +575,187 @@ const ExperimentDetailView: React.FC<{
|
||||
<span className="ml-2 bg-indigo-500/20 text-indigo-400 border border-indigo-500/20 text-[9px] font-bold uppercase tracking-wider px-1.5 py-0.5 rounded">PRIMARY</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="py-4 text-[14px] text-[#64748b] font-medium">{formatMetricValue(res.treatment_before, metric)}</td>
|
||||
<td className="py-4 text-[14px] text-white font-bold">{formatMetricValue(res.treatment_after, metric)}</td>
|
||||
<td className={`py-4 text-sm font-bold flex items-center gap-1 ${isGood ? 'text-emerald-400' : 'text-[#f43f5e]'}`}>
|
||||
<div className="flex flex-col">
|
||||
<span className="flex items-center gap-1">
|
||||
<svg className={`w-3.5 h-3.5 ${isGood ? (res.lift_percent >= 0 ? '' : 'rotate-180') : 'rotate-180'}`} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}><path strokeLinecap="round" strokeLinejoin="round" d="M5 15l7-7 7 7" /></svg>
|
||||
{res.lift_percent >= 0 ? '+' : ''}{res.lift_percent.toFixed(1)}%
|
||||
</span>
|
||||
<span className="text-[10px] text-[#64748b] font-medium mt-0.5 ml-1">
|
||||
DiD: {res.did_estimate >= 0 ? '+' : ''}{formatMetricValue(res.did_estimate, metric)}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-4 text-[14px] text-[#64748b] font-medium">—</td>
|
||||
<td className="py-4 text-[14px] text-white font-bold">—</td>
|
||||
<td className="py-4 text-sm font-bold text-slate-500">—</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content Comparison */}
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-4 mt-8">
|
||||
<svg className="w-5 h-5 text-indigo-400" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h7" /></svg>
|
||||
<h2 className="text-xl font-bold text-white">Content Comparison</h2>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="bg-[#0f172a] border border-white/5 rounded-2xl p-6 relative overflow-hidden">
|
||||
<div className="text-[10px] uppercase tracking-widest text-[#64748b] font-black mb-3 text-right">Original</div>
|
||||
<p className="text-sm text-[#94a3b8] leading-relaxed font-medium">
|
||||
{experiment.changes[0]?.before_value || 'Original content based on baseline metrics.'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-[#1e1b4b]/40 border border-[#4f46e5]/40 rounded-2xl p-6 relative overflow-hidden">
|
||||
<div className="text-[10px] uppercase tracking-widest text-[#818cf8] font-black mb-3 text-right">Variant B (Winner)</div>
|
||||
<p className="text-sm text-white leading-relaxed font-bold">
|
||||
{experiment.changes[0]?.after_value || 'New optimized content tested in this experiment.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Right Column (Widgets) */}
|
||||
<div className="space-y-8">
|
||||
|
||||
{/* Probability of Success */}
|
||||
<div className="bg-[#0b1120] border-l lg:border-t-0 border-[#1e293b] lg:pl-10 h-full">
|
||||
<div className="flex items-center gap-2 mb-8">
|
||||
<svg className="w-5 h-5 text-indigo-400" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /></svg>
|
||||
<h2 className="text-xl font-bold text-white">Probability of Success</h2>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center justify-center my-10 relative">
|
||||
<svg width="180" height="180" className="transform -rotate-90 text-emerald-500/10">
|
||||
<circle cx="90" cy="90" r={radius} stroke="currentColor" strokeWidth="12" fill="transparent" />
|
||||
<circle cx="90" cy="90" r={radius} stroke="#10b981" strokeWidth="12" fill="transparent"
|
||||
strokeDasharray={circumference}
|
||||
strokeDashoffset={offset}
|
||||
strokeLinecap="round"
|
||||
className="transition-all duration-1000 ease-in-out"
|
||||
/>
|
||||
</svg>
|
||||
<div className="absolute inset-0 flex items-center justify-center flex-col">
|
||||
<span className="text-5xl font-black text-white">{prob}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center mb-10">
|
||||
<span className="bg-emerald-500/10 text-[#10b981] border border-emerald-500/20 text-[10px] font-bold uppercase tracking-widest px-4 py-2 rounded-full inline-flex items-center gap-1.5 shadow-[0_0_15px_rgba(16,185,129,0.1)]">
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
Significant Confidence Reached
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Estimated Annual Impact */}
|
||||
<div className="border-t border-white/5 pt-8">
|
||||
<div className="flex items-center gap-2 mb-8">
|
||||
<svg className="w-5 h-5 text-indigo-400" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 2v20m0 0l-3-3m3 3l3-3m-6-11h6c1.11 0 2 .89 2 2s-.89 2-2 2h-6c-1.11 0-2 .89-2 2s.89 2 2 2h2" /></svg>
|
||||
<h2 className="text-lg font-bold text-white">Estimated Annual Impact</h2>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<div className="flex justify-between text-[10px] uppercase font-black tracking-widest mb-2.5">
|
||||
<span className="text-emerald-400 opacity-80">Optimistic Forecast</span>
|
||||
<span className="text-emerald-400">$65,000</span>
|
||||
</div>
|
||||
<div className="h-2.5 w-full bg-[#1e293b] rounded-full overflow-hidden leading-none">
|
||||
<div className="h-full bg-[#10b981] rounded-full w-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between text-[10px] uppercase font-black tracking-widest mb-2.5">
|
||||
<span className="text-indigo-400 opacity-80">Expected Gain</span>
|
||||
<span className="text-indigo-400">$42,000</span>
|
||||
</div>
|
||||
<div className="h-2.5 w-full bg-[#1e293b] rounded-full overflow-hidden leading-none">
|
||||
<div className="h-full bg-[#6366f1] rounded-full w-[65%]"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between text-[10px] uppercase font-black tracking-widest mb-2.5">
|
||||
<span className="text-[#64748b]">Conservative Baseline</span>
|
||||
<span className="text-[#64748b]">$18,000</span>
|
||||
</div>
|
||||
<div className="h-2.5 w-full bg-[#1e293b] rounded-full overflow-hidden leading-none">
|
||||
<div className="h-full bg-[#475569] rounded-full w-[25%]"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Info Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div className="bg-[#1e293b]/40 border border-white/5 rounded-3xl p-6 md:p-8 relative">
|
||||
<div className="w-12 h-12 rounded-full bg-indigo-500/10 flex items-center justify-center text-indigo-400 mb-6">
|
||||
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
</div>
|
||||
<div className="text-[10px] font-black uppercase tracking-widest text-[#94a3b8] mb-1">Test Timeline</div>
|
||||
<div className="text-2xl font-black text-white mb-6">12 Weeks</div>
|
||||
|
||||
<div className="border-t border-[#334155] pt-5 mb-5 space-y-1">
|
||||
<div className="text-[10px] font-black uppercase tracking-widest text-[#64748b]">Active Test Period</div>
|
||||
<div className="text-[15px] font-bold text-white">Jan 01 - Mar 25, 2026</div>
|
||||
<div className="text-[11px] text-[#4f46e5] font-semibold">Weeks 1-12 (2026)</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<div className="text-[10px] font-black uppercase tracking-widest text-[#64748b]">Comparison Baseline</div>
|
||||
<div className="text-[15px] font-bold text-[#94a3b8]">Oct 01 - Dec 24, 2025</div>
|
||||
<div className="text-[11px] text-[#64748b] font-medium">Weeks 40-51 (2025)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-[#6366f1] rounded-3xl p-8 relative overflow-hidden flex flex-col justify-center">
|
||||
<div className="absolute -bottom-10 -right-10 opacity-[0.15]">
|
||||
<svg className="w-64 h-64" viewBox="0 0 24 24" fill="currentColor"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" /></svg>
|
||||
</div>
|
||||
<div className="relative z-10 flex items-center gap-2 text-[#e0e7ff] text-[10px] font-black uppercase tracking-widest mb-4">
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" /></svg>
|
||||
Statistical Insight
|
||||
</div>
|
||||
<p className="relative z-10 text-xl font-bold text-white leading-relaxed">
|
||||
Results are normalized against the Pre-test Baseline to account for category seasonality.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-[#1e293b]/40 border border-white/5 rounded-3xl p-6 md:p-8 flex flex-col justify-center items-start">
|
||||
<div className="w-16 h-16 rounded-2xl border border-amber-500/20 bg-amber-500/10 flex items-center justify-center text-amber-500 mb-6">
|
||||
<span className="text-3xl font-serif">T</span>
|
||||
</div>
|
||||
<div className="text-[10px] font-black uppercase tracking-widest text-white mb-3">Content Strategy</div>
|
||||
<p className="text-[15px] text-[#94a3b8] leading-relaxed max-w-xs font-medium">
|
||||
Focus on high-velocity benefit descriptors in Treatment B.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Back Button + Header */}
|
||||
<button onClick={onBack} className="flex items-center gap-1.5 text-sm text-slate-400 hover:text-white transition-colors mb-6">
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
Back to experiments
|
||||
</button>
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex flex-col md:flex-row md:items-start justify-between gap-4 mb-8">
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<h1 className="text-2xl font-bold text-white tracking-tight">{experiment.name}</h1>
|
||||
<StatusBadge status={experiment.status} />
|
||||
<TypeBadge type={experiment.type} />
|
||||
</div>
|
||||
{experiment.description && (
|
||||
<p className="text-sm text-slate-400 max-w-2xl">{experiment.description}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
{experiment.status === 'planned' && (
|
||||
<button onClick={() => handleUpdateStatus('active')} className="px-3 py-2 bg-emerald-600 hover:bg-emerald-500 text-white rounded-lg text-sm font-medium transition-colors">
|
||||
Start Experiment
|
||||
</button>
|
||||
)}
|
||||
{experiment.status === 'active' && (
|
||||
<button onClick={() => handleUpdateStatus('completed')} className="px-3 py-2 bg-slate-600 hover:bg-slate-500 text-white rounded-lg text-sm font-medium transition-colors">
|
||||
Complete
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={handleRunAnalysis}
|
||||
disabled={analyzing || salesData.length === 0}
|
||||
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed text-white rounded-lg text-sm font-medium transition-colors flex items-center gap-2"
|
||||
>
|
||||
{analyzing ? (
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-2 border-white border-t-transparent" />
|
||||
) : (
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z" />
|
||||
</svg>
|
||||
)}
|
||||
Run Analysis
|
||||
</button>
|
||||
<button
|
||||
onClick={onEdit}
|
||||
className="p-2 text-slate-400 hover:text-indigo-400 hover:bg-indigo-500/10 rounded-lg transition-colors"
|
||||
title="Edit experiment"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={handleDelete}
|
||||
className="p-2 text-slate-400 hover:text-red-400 hover:bg-red-500/10 rounded-lg transition-colors"
|
||||
title="Delete experiment"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Verdict Card (if results exist) */}
|
||||
{experiment.verdict && primaryResult && (
|
||||
<div className={`mb-8 p-6 rounded-2xl border ${getVerdictColor(experiment.verdict)} relative overflow-hidden`}>
|
||||
<div className="relative z-10 flex flex-col md:flex-row items-center gap-6">
|
||||
<div className="text-center md:text-left">
|
||||
<div className="text-xs uppercase tracking-widest opacity-60 mb-1">Primary Metric Verdict</div>
|
||||
<div className="text-3xl font-bold">
|
||||
{getVerdictLabel(experiment.verdict)}
|
||||
</div>
|
||||
<div className="text-sm opacity-80 mt-1">
|
||||
{METRIC_LABELS[experiment.primary_metric] || experiment.primary_metric}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1" />
|
||||
<div className="flex items-center gap-8">
|
||||
<div className="text-center">
|
||||
<div className="text-xs text-slate-400 mb-1">Lift</div>
|
||||
<div className={`text-2xl font-bold ${primaryResult.lift_percent >= 0 ? 'text-emerald-400' : 'text-red-400'}`}>
|
||||
{primaryResult.lift_percent >= 0 ? '+' : ''}{primaryResult.lift_percent}%
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-xs text-slate-400 mb-1">Confidence</div>
|
||||
<div className="text-2xl font-bold text-white">
|
||||
{Math.round((experiment.verdict_probability || 0) * 100)}%
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-xs text-slate-400 mb-1">DiD Effect</div>
|
||||
<div className="text-2xl font-bold text-white">
|
||||
{primaryResult.did_estimate >= 0 ? '+' : ''}{formatMetricValue(primaryResult.did_estimate, experiment.primary_metric)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Confidence bar */}
|
||||
<div className="mt-4 h-1.5 bg-slate-700/50 rounded-full overflow-hidden">
|
||||
<div
|
||||
className={`h-full rounded-full transition-all duration-500 ${experiment.verdict === 'winner' ? 'bg-emerald-400' :
|
||||
experiment.verdict === 'loser' ? 'bg-red-400' : 'bg-slate-400'
|
||||
}`}
|
||||
style={{ width: `${Math.round((experiment.verdict_probability || 0) * 100)}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Info Grid */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
|
||||
{/* Hypothesis */}
|
||||
<Card title="Hypothesis" className="lg:col-span-2">
|
||||
<p className="text-sm text-slate-300 leading-relaxed">
|
||||
{experiment.hypothesis || 'No hypothesis defined'}
|
||||
</p>
|
||||
</Card>
|
||||
|
||||
{/* Timeline */}
|
||||
<Card title="Timeline">
|
||||
<div className="space-y-3">
|
||||
{experiment.baseline_start_date && experiment.baseline_end_date && (
|
||||
<div className="flex flex-col mb-3 pb-3 border-b border-slate-700/50">
|
||||
<span className="text-xs font-semibold text-indigo-400 mb-1 uppercase tracking-wider">Baseline Period</span>
|
||||
<span className="text-white text-sm">
|
||||
{new Date(experiment.baseline_start_date).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })}
|
||||
{' — '}
|
||||
{new Date(experiment.baseline_end_date).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })}
|
||||
</span>
|
||||
<span className="text-[10px] text-slate-500 mt-0.5">Custom Seasonal Comparison</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-slate-400">Start</span>
|
||||
<span className="text-white font-medium">{new Date(experiment.start_date).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })}</span>
|
||||
</div>
|
||||
{experiment.end_date && (
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-slate-400">End</span>
|
||||
<span className="text-white font-medium">{new Date(experiment.end_date).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-slate-400">Primary Metric</span>
|
||||
<span className="text-indigo-400 font-medium">{METRIC_LABELS[experiment.primary_metric] || experiment.primary_metric}</span>
|
||||
</div>
|
||||
{experiment.owner && (
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-slate-400">Owner</span>
|
||||
<span className="text-white">{experiment.owner}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-slate-400">Marketplace</span>
|
||||
<span className="text-white">{experiment.marketplace || 'All'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Change Annotations */}
|
||||
{experiment.changes && experiment.changes.length > 0 && (
|
||||
<Card title="Changes Made" className="mb-8">
|
||||
<div className="space-y-2">
|
||||
{experiment.changes.map((c, i) => (
|
||||
<div key={i} className="flex items-center gap-3 py-2 px-3 bg-slate-800/50 rounded-lg">
|
||||
<span className="text-xs font-medium text-slate-400 uppercase tracking-wide w-24 shrink-0">{c.field}</span>
|
||||
<code className="text-sm text-red-400/80 bg-red-500/10 px-2 py-0.5 rounded">{c.before_value}</code>
|
||||
<svg className="w-4 h-4 text-slate-500 shrink-0" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3" />
|
||||
</svg>
|
||||
<code className="text-sm text-emerald-400/80 bg-emerald-500/10 px-2 py-0.5 rounded">{c.after_value}</code>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* ASINs */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
||||
<Card title={`Treatment ASINs (${experiment.asins?.length || 0})`}>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{(experiment.asins || []).map(a => (
|
||||
<span key={a} className="text-xs font-mono bg-indigo-500/10 text-indigo-300 px-2 py-1 rounded border border-indigo-500/20">{a}</span>
|
||||
))}
|
||||
{(!experiment.asins || experiment.asins.length === 0) && (
|
||||
<span className="text-sm text-slate-500">No ASINs defined</span>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
<Card title={`Control Group (${experiment.control_asins?.length || 0})`}>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{(experiment.control_asins || []).map(a => (
|
||||
<span key={a} className="text-xs font-mono bg-slate-500/10 text-slate-300 px-2 py-1 rounded border border-slate-500/20">{a}</span>
|
||||
))}
|
||||
{(!experiment.control_asins || experiment.control_asins.length === 0) && (
|
||||
<span className="text-sm text-slate-500">No control group — using simple before/after comparison</span>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Trend Chart */}
|
||||
{trendData.length > 2 && experiment.did_results && (
|
||||
<Card title="Performance Trend: Actual vs Counterfactual" className="mb-8">
|
||||
<div className="h-80">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart data={trendData} margin={{ top: 10, right: 20, left: 10, bottom: 5 }}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#334155" />
|
||||
<XAxis
|
||||
dataKey="week"
|
||||
tick={{ fill: '#94a3b8', fontSize: 11 }}
|
||||
tickFormatter={w => w.split('-')[1] || w}
|
||||
stroke="#475569"
|
||||
/>
|
||||
<YAxis tick={{ fill: '#94a3b8', fontSize: 11 }} stroke="#475569" />
|
||||
<RechartsTooltip
|
||||
content={({ active, payload, label }) => {
|
||||
if (!active || !payload?.length) return null;
|
||||
const actual = payload.find(p => p.dataKey === 'actual');
|
||||
const counter = payload.find(p => p.dataKey === 'counterfactual');
|
||||
}
|
||||
const isPositiveGood = !['acos', 'bsr'].includes(metric);
|
||||
const isGood = isPositiveGood ? res.lift_percent >= 0 : res.lift_percent <= 0;
|
||||
return (
|
||||
<div className="bg-slate-800 border border-slate-700 rounded-lg p-3 shadow-xl">
|
||||
<div className="text-xs text-slate-400 mb-2">{label}</div>
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<div className="w-2 h-2 rounded-full bg-indigo-500" />
|
||||
<span className="text-slate-300">Actual:</span>
|
||||
<span className="text-white font-medium">{formatMetricValue(Number(actual?.value || 0), experiment.primary_metric)}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm mt-1">
|
||||
<div className="w-2 h-2 rounded-full bg-slate-400" />
|
||||
<span className="text-slate-300">Counterfactual:</span>
|
||||
<span className="text-white font-medium">{formatMetricValue(Number(counter?.value || 0), experiment.primary_metric)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<tr key={metric} className="hover:bg-white/[0.02] transition-colors">
|
||||
<td className="py-4 text-[13px] font-bold text-slate-200">
|
||||
{METRIC_LABELS[metric] || metric}
|
||||
{metric === experiment.primary_metric && (
|
||||
<span className="ml-2 bg-indigo-500/20 text-indigo-400 border border-indigo-500/20 text-[9px] font-bold uppercase tracking-wider px-1.5 py-0.5 rounded">PRIMARY</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="py-4 text-[14px] text-[#64748b] font-medium">{formatMetricValue(res.treatment_before, metric)}</td>
|
||||
<td className="py-4 text-[14px] text-white font-bold">{formatMetricValue(res.treatment_after, metric)}</td>
|
||||
<td className={`py-4 text-sm font-bold flex items-center gap-1 ${isGood ? 'text-emerald-400' : 'text-[#f43f5e]'}`}>
|
||||
<div className="flex flex-col">
|
||||
<span className="flex items-center gap-1">
|
||||
<svg className={`w-3.5 h-3.5 ${isGood ? (res.lift_percent >= 0 ? '' : 'rotate-180') : 'rotate-180'}`} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}><path strokeLinecap="round" strokeLinejoin="round" d="M5 15l7-7 7 7" /></svg>
|
||||
{res.lift_percent >= 0 ? '+' : ''}{res.lift_percent.toFixed(1)}%
|
||||
</span>
|
||||
<span className="text-[10px] text-[#64748b] font-medium mt-0.5 ml-1">
|
||||
DiD: {res.did_estimate >= 0 ? '+' : ''}{formatMetricValue(res.did_estimate, metric)}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{experiment.start_date && (
|
||||
<ReferenceLine
|
||||
x={(() => {
|
||||
const sd = new Date(experiment.start_date);
|
||||
const week = Math.ceil((sd.getTime() - new Date(sd.getFullYear(), 0, 1).getTime()) / (7 * 86400000));
|
||||
return `${sd.getFullYear()}-W${String(week).padStart(2, '0')}`;
|
||||
})()}
|
||||
stroke="#f59e0b"
|
||||
strokeDasharray="4 4"
|
||||
label={{ value: 'Start', position: 'top', fill: '#f59e0b', fontSize: 11 }}
|
||||
/>
|
||||
)}
|
||||
<Line type="monotone" dataKey="actual" stroke="#6366f1" strokeWidth={2.5} dot={false} name="Actual" />
|
||||
<Line type="monotone" dataKey="counterfactual" stroke="#64748b" strokeWidth={2} strokeDasharray="6 3" dot={false} name="Counterfactual" />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-6 mt-3">
|
||||
<div className="flex items-center gap-2 text-xs text-slate-400">
|
||||
<div className="w-6 h-0.5 bg-indigo-500 rounded" />
|
||||
Actual performance
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs text-slate-400">
|
||||
<div className="w-6 h-0.5 bg-slate-500 rounded" style={{ backgroundImage: 'repeating-linear-gradient(90deg, #64748b 0, #64748b 4px, transparent 4px, transparent 7px)' }} />
|
||||
Counterfactual (without change)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* DiD Metric Cards */}
|
||||
{didResults && (
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 mb-8">
|
||||
{(Object.entries(didResults.metrics) as [string, DiDMetricResult][]).map(([metric, result]) => {
|
||||
const isPrimary = metric === experiment.primary_metric;
|
||||
return (
|
||||
<div
|
||||
key={metric}
|
||||
className={`p-4 rounded-xl border transition-colors ${isPrimary ? 'border-indigo-500/40 bg-indigo-500/5' : 'border-slate-800 bg-slate-900/50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="text-xs font-medium text-slate-400 uppercase tracking-wide">
|
||||
{METRIC_LABELS[metric] || metric}
|
||||
</span>
|
||||
{isPrimary && (
|
||||
<span className="text-[10px] bg-indigo-500/20 text-indigo-400 px-1.5 py-0.5 rounded font-medium">PRIMARY</span>
|
||||
)}
|
||||
{/* Content Comparison */}
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-4 mt-8">
|
||||
<svg className="w-5 h-5 text-indigo-400" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h7" /></svg>
|
||||
<h2 className="text-xl font-bold text-white">Content Comparison</h2>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="bg-[#0f172a] border border-white/5 rounded-2xl p-6 relative overflow-hidden">
|
||||
<div className="text-[10px] uppercase tracking-widest text-[#64748b] font-black mb-3 text-right">Original</div>
|
||||
<p className="text-sm text-[#94a3b8] leading-relaxed font-medium">
|
||||
{experiment.changes[0]?.before_value || 'Original content based on baseline metrics.'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-[#1e1b4b]/40 border border-[#4f46e5]/40 rounded-2xl p-6 relative overflow-hidden">
|
||||
<div className="text-[10px] uppercase tracking-widest text-[#818cf8] font-black mb-3 text-right">Variant B (Winner)</div>
|
||||
<p className="text-sm text-white leading-relaxed font-bold">
|
||||
{experiment.changes[0]?.after_value || 'New optimized content tested in this experiment.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Right Column (Widgets) */}
|
||||
<div className="space-y-8">
|
||||
|
||||
{/* Probability of Success */}
|
||||
<div className="bg-[#0b1120] border-l lg:border-t-0 border-[#1e293b] lg:pl-10 h-full">
|
||||
<div className="flex items-center gap-2 mb-8">
|
||||
<svg className="w-5 h-5 text-indigo-400" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /></svg>
|
||||
<h2 className="text-xl font-bold text-white">Probability of Success</h2>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center justify-center my-10 relative">
|
||||
<svg width="180" height="180" className="transform -rotate-90 text-emerald-500/10">
|
||||
<circle cx="90" cy="90" r={radius} stroke="currentColor" strokeWidth="12" fill="transparent" />
|
||||
<circle cx="90" cy="90" r={radius} stroke="#10b981" strokeWidth="12" fill="transparent"
|
||||
strokeDasharray={circumference}
|
||||
strokeDashoffset={offset}
|
||||
strokeLinecap="round"
|
||||
className="transition-all duration-1000 ease-in-out"
|
||||
/>
|
||||
</svg>
|
||||
<div className="absolute inset-0 flex items-center justify-center flex-col">
|
||||
<span className="text-5xl font-black text-white">{prob}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center mb-10">
|
||||
<span className="bg-emerald-500/10 text-[#10b981] border border-emerald-500/20 text-[10px] font-bold uppercase tracking-widest px-4 py-2 rounded-full inline-flex items-center gap-1.5 shadow-[0_0_15px_rgba(16,185,129,0.1)]">
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
Significant Confidence Reached
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Estimated Annual Impact */}
|
||||
<div className="border-t border-white/5 pt-8">
|
||||
<div className="flex items-center gap-2 mb-8">
|
||||
<svg className="w-5 h-5 text-indigo-400" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 2v20m0 0l-3-3m3 3l3-3m-6-11h6c1.11 0 2 .89 2 2s-.89 2-2 2h-6c-1.11 0-2 .89-2 2s.89 2 2 2h2" /></svg>
|
||||
<h2 className="text-lg font-bold text-white">Estimated Annual Impact</h2>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<div className="flex justify-between text-[10px] uppercase font-black tracking-widest mb-2.5">
|
||||
<span className="text-emerald-400 opacity-80">Optimistic Forecast</span>
|
||||
<span className="text-emerald-400">$65,000</span>
|
||||
</div>
|
||||
<div className="h-2.5 w-full bg-[#1e293b] rounded-full overflow-hidden leading-none">
|
||||
<div className="h-full bg-[#10b981] rounded-full w-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`text-xl font-bold ${result.lift_percent >= 0 ? 'text-emerald-400' : 'text-red-400'}`}>
|
||||
{result.lift_percent >= 0 ? '+' : ''}{result.lift_percent}%
|
||||
<div>
|
||||
<div className="flex justify-between text-[10px] uppercase font-black tracking-widest mb-2.5">
|
||||
<span className="text-indigo-400 opacity-80">Expected Gain</span>
|
||||
<span className="text-indigo-400">$42,000</span>
|
||||
</div>
|
||||
<div className="h-2.5 w-full bg-[#1e293b] rounded-full overflow-hidden leading-none">
|
||||
<div className="h-full bg-[#6366f1] rounded-full w-[65%]"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs text-slate-500 mt-1">
|
||||
DiD: {result.did_estimate >= 0 ? '+' : ''}{formatMetricValue(result.did_estimate, metric)}
|
||||
</div>
|
||||
<div className="mt-2 flex items-center gap-2">
|
||||
<div className="flex-1 h-1 bg-slate-700 rounded-full overflow-hidden">
|
||||
<div
|
||||
className={`h-full rounded-full ${result.posterior_prob_positive >= 0.9 ? 'bg-emerald-400' :
|
||||
result.posterior_prob_positive <= 0.1 ? 'bg-red-400' : 'bg-slate-400'
|
||||
}`}
|
||||
style={{ width: `${Math.round(result.posterior_prob_positive * 100)}%` }}
|
||||
/>
|
||||
<div>
|
||||
<div className="flex justify-between text-[10px] uppercase font-black tracking-widest mb-2.5">
|
||||
<span className="text-[#64748b]">Conservative Baseline</span>
|
||||
<span className="text-[#64748b]">$18,000</span>
|
||||
</div>
|
||||
<div className="h-2.5 w-full bg-[#1e293b] rounded-full overflow-hidden leading-none">
|
||||
<div className="h-full bg-[#475569] rounded-full w-[25%]"></div>
|
||||
</div>
|
||||
<span className="text-[10px] text-slate-500">{Math.round(result.posterior_prob_positive * 100)}%</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Before / After Summary Table */}
|
||||
{didResults && (
|
||||
<Card title="Before vs After Comparison" className="mb-8">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-slate-800">
|
||||
<Th>Metric</Th>
|
||||
<Th align="right">Treatment Before</Th>
|
||||
<Th align="right">Treatment After</Th>
|
||||
<Th align="right">Control Before</Th>
|
||||
<Th align="right">Control After</Th>
|
||||
<Th align="right">DiD Effect</Th>
|
||||
<Th align="right">Lift</Th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-800/50">
|
||||
{(Object.entries(didResults.metrics) as [string, DiDMetricResult][]).map(([metric, r]) => (
|
||||
<tr key={metric} className={metric === experiment.primary_metric ? 'bg-indigo-500/5' : ''}>
|
||||
<td className="px-4 py-2.5 text-slate-300 font-medium">{METRIC_LABELS[metric] || metric}</td>
|
||||
<td className="px-4 py-2.5 text-right text-slate-400">{formatMetricValue(r.treatment_before, metric)}</td>
|
||||
<td className="px-4 py-2.5 text-right text-white font-medium">{formatMetricValue(r.treatment_after, metric)}</td>
|
||||
<td className="px-4 py-2.5 text-right text-slate-400">{typeof r.control_before === 'number' ? formatMetricValue(r.control_before, metric) : '—'}</td>
|
||||
<td className="px-4 py-2.5 text-right text-slate-400">{typeof r.control_after === 'number' ? formatMetricValue(r.control_after, metric) : '—'}</td>
|
||||
<td className={`px-4 py-2.5 text-right font-medium ${r.did_estimate >= 0 ? 'text-emerald-400' : 'text-red-400'}`}>
|
||||
{r.did_estimate >= 0 ? '+' : ''}{formatMetricValue(r.did_estimate, metric)}
|
||||
</td>
|
||||
<td className={`px-4 py-2.5 text-right font-bold ${r.lift_percent >= 0 ? 'text-emerald-400' : 'text-red-400'}`}>
|
||||
{r.lift_percent >= 0 ? '+' : ''}{r.lift_percent}%
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Learnings */}
|
||||
<Card title="Learnings & Conclusions" className="mb-8">
|
||||
{editing ? (
|
||||
<div className="space-y-3">
|
||||
<textarea
|
||||
value={editLearnings}
|
||||
onChange={e => setEditLearnings(e.target.value)}
|
||||
rows={5}
|
||||
className="w-full bg-slate-800 border border-slate-700 rounded-lg p-3 text-sm text-slate-200 placeholder-slate-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 resize-y"
|
||||
placeholder="What did you learn from this experiment?"
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<button onClick={handleSaveLearnings} className="px-3 py-1.5 bg-indigo-600 hover:bg-indigo-500 text-white text-sm rounded-lg transition-colors">
|
||||
Save
|
||||
</button>
|
||||
<button onClick={() => { setEditing(false); setEditLearnings(experiment.learnings || ''); }} className="px-3 py-1.5 text-slate-400 hover:text-white text-sm transition-colors">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
onClick={() => setEditing(true)}
|
||||
className="cursor-pointer hover:bg-slate-800/30 rounded-lg p-2 -m-2 transition-colors"
|
||||
>
|
||||
{experiment.learnings ? (
|
||||
<p className="text-sm text-slate-300 leading-relaxed whitespace-pre-wrap">{experiment.learnings}</p>
|
||||
) : (
|
||||
<p className="text-sm text-slate-500 italic">Click to add learnings...</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Info Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div className="bg-[#1e293b]/40 border border-white/5 rounded-3xl p-6 md:p-8 relative">
|
||||
<div className="w-12 h-12 rounded-full bg-indigo-500/10 flex items-center justify-center text-indigo-400 mb-6">
|
||||
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</>
|
||||
<div className="text-[10px] font-black uppercase tracking-widest text-[#94a3b8] mb-1">Test Timeline</div>
|
||||
<div className="text-2xl font-black text-white mb-6">12 Weeks</div>
|
||||
|
||||
<div className="border-t border-[#334155] pt-5 mb-5 space-y-1">
|
||||
<div className="text-[10px] font-black uppercase tracking-widest text-[#64748b]">Active Test Period</div>
|
||||
<div className="text-[15px] font-bold text-white">Jan 01 - Mar 25, 2026</div>
|
||||
<div className="text-[11px] text-[#4f46e5] font-semibold">Weeks 1-12 (2026)</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<div className="text-[10px] font-black uppercase tracking-widest text-[#64748b]">Comparison Baseline</div>
|
||||
<div className="text-[15px] font-bold text-[#94a3b8]">Oct 01 - Dec 24, 2025</div>
|
||||
<div className="text-[11px] text-[#64748b] font-medium">Weeks 40-51 (2025)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-[#6366f1] rounded-3xl p-8 relative overflow-hidden flex flex-col justify-center">
|
||||
<div className="absolute -bottom-10 -right-10 opacity-[0.15]">
|
||||
<svg className="w-64 h-64" viewBox="0 0 24 24" fill="currentColor"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" /></svg>
|
||||
</div>
|
||||
<div className="relative z-10 flex items-center gap-2 text-[#e0e7ff] text-[10px] font-black uppercase tracking-widest mb-4">
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" /></svg>
|
||||
Statistical Insight
|
||||
</div>
|
||||
<p className="relative z-10 text-xl font-bold text-white leading-relaxed">
|
||||
Results are normalized against the Pre-test Baseline to account for category seasonality.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-[#1e293b]/40 border border-white/5 rounded-3xl p-6 md:p-8 flex flex-col justify-center items-start">
|
||||
<div className="w-16 h-16 rounded-2xl border border-amber-500/20 bg-amber-500/10 flex items-center justify-center text-amber-500 mb-6">
|
||||
<span className="text-3xl font-serif">T</span>
|
||||
</div>
|
||||
<div className="text-[10px] font-black uppercase tracking-widest text-white mb-3">Content Strategy</div>
|
||||
<p className="text-[15px] text-[#94a3b8] leading-relaxed max-w-xs font-medium">
|
||||
Focus on high-velocity benefit descriptors in Treatment B.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user