import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { Experiment, ExperimentCreateInput, ExperimentType, ExperimentStatus, ExperimentMetric, CombinedKPIs } from '../types'; import { getExperiment, updateExperiment, deleteExperiment, calculateExperimentPerformance } from '../services/experiments'; import { ExperimentStatusBadge, ExperimentTypeBadge } from './ExperimentBadge'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip as RechartsTooltip, ResponsiveContainer, ReferenceLine } from 'recharts'; interface ExperimentDetailProps { experimentId: string | null; onClose: () => void; salesData?: any[]; } const ExperimentDetail: React.FC = ({ experimentId, onClose, salesData = [] }) => { const [experiment, setExperiment] = useState(null); const [loading, setLoading] = useState(false); const [editing, setEditing] = useState(false); const [formData, setFormData] = useState>({}); const loadExperiment = useCallback(async () => { if (!experimentId) return; setLoading(true); try { const data = await getExperiment(experimentId); setExperiment(data); setFormData(data || {}); } catch (e: any) { console.error('Failed to load experiment:', e); } finally { setLoading(false); } }, [experimentId]); useEffect(() => { if (experimentId) { loadExperiment(); } }, [experimentId, loadExperiment]); const handleSave = async () => { if (!experimentId) return; setLoading(true); try { const updated = await updateExperiment(experimentId, formData); setExperiment(updated); setEditing(false); } catch (e: any) { alert(`Error saving: ${e.message}`); } finally { setLoading(false); } }; const handleCalculateResults = async () => { if (!experiment || !salesData.length) return; setLoading(true); try { const perf = await calculateExperimentPerformance(experiment, salesData); const updated = await updateExperiment(experiment.id, perf); setExperiment(updated); } catch (e: any) { alert(`Error calculating results: ${e.message}`); } finally { setLoading(false); } }; const chartData = useMemo(() => { if (!experiment || !salesData.length) return []; const asinSet = new Set((experiment.asins || []).map(a => a.toUpperCase())); const weeklyMap = new Map(); salesData.forEach(r => { const asin = (r.asin || '').toUpperCase(); if (!asinSet.has(asin)) return; const weekNum = r.week || 1; const yearStr = r.year || new Date().getFullYear(); const weekStr = `${yearStr}-W${String(weekNum).padStart(2, '0')}`; if (!weeklyMap.has(weekStr)) { const d = new Date(yearStr, 0, 1 + (weekNum - 1) * 7); weeklyMap.set(weekStr, { name: weekStr, timestamp: d.getTime(), units: 0, revenue: 0, gv: 0, spend: 0, }); } const w = weeklyMap.get(weekStr); w.units += r.unitsTotal || 0; w.revenue += r.salesTotal || 0; w.gv += r.glanceViews || 0; w.spend += r.cost || 0; }); const arr = Array.from(weeklyMap.values()).map(w => ({ ...w, cvr: w.gv > 0 ? (w.units / w.gv) * 100 : 0, acos: w.revenue > 0 ? (w.spend / w.revenue) * 100 : 0 })).sort((a, b) => a.timestamp - b.timestamp); return arr.slice(-16); // Let's keep the last 16 weeks to ensure enough window before and during }, [experiment, salesData]); const getChartMetricColor = (metric: string) => { switch (metric) { case 'cvr': return '#34d399'; // emerald case 'gv': return '#a78bfa'; // purple case 'acos': return '#fb7185'; // rose case 'revenue': return '#fbbf24'; // amber default: return '#818cf8'; // indigo for units } }; const formatChartMetric = (val: number, metric: string) => { if (metric === 'cvr' || metric === 'acos' || metric === 'ctr') return `${val.toFixed(1)}%`; if (metric === 'revenue' || metric === 'cost') return `€${Math.round(val).toLocaleString('de-DE')}`; return Math.round(val).toLocaleString('de-DE'); }; if (!experimentId) return null; return (
{/* Header */}
{editing ? ( setFormData({ ...formData, name: e.target.value })} className="text-xl font-bold bg-slate-800 border border-slate-600 rounded-lg px-3 py-1 text-white" /> ) : (

{experiment?.name || 'Loading...'}

)} {experiment && (
)}
{editing ? ( <> ) : ( <> )}
{/* Content */}
{loading && !experiment ? (
) : experiment ? (
{/* Performance Cards */} {(experiment.experiment_units !== undefined) && (
{experiment.type === 'pricing' && ( <> )} {experiment.type === 'advertising' && ( <> )} {experiment.type === 'content' && ( <> )} {experiment.type === 'promotion' && ( <> )} = 0 ? '+' : ''}${experiment.actual_lift_percent}%` : '—'} color={experiment.actual_lift_percent !== undefined && experiment.actual_lift_percent >= 0 ? 'emerald' : 'red'} />
)} {/* Calculate Results Button */} {salesData.length > 0 && ( )} {/* Dynamic Metric Chart */} {chartData.length > 0 && (

Timeline Impact ({experiment.primary_metric.toUpperCase()})

val.split('-')[1]} // Just show W12 /> formatChartMetric(val, experiment.primary_metric)} width={60} /> { if (experiment.primary_metric === 'cvr') return [`${value.toFixed(1)}%`, 'CVR']; if (experiment.primary_metric === 'acos') return [`${value.toFixed(1)}%`, 'ACOS']; if (experiment.primary_metric === 'revenue') return [`€${value.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}`, 'Rev']; return [value.toLocaleString('de-DE'), 'Units']; }} /> {/* Reference Line for Start Date */} { const dDate = new Date(experiment.start_date); const jan1 = new Date(dDate.getFullYear(), 0, 1); const dayOfYear = Math.floor((dDate.getTime() - jan1.getTime()) / 86400000) + 1; const wN = Math.ceil((dayOfYear + jan1.getDay()) / 7); return `${dDate.getFullYear()}-W${String(wN).padStart(2, '0')}`; })()} stroke="#e2e8f0" strokeDasharray="3 3" label={{ position: 'top', value: 'Started', fill: '#e2e8f0', fontSize: 10 }} />
)} {/* Details Grid */}
{/* Left Column */}
{editing ? ( <>