import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { Experiment, ExperimentCreateInput, ExperimentType, ExperimentStatus, ExperimentMetric, CombinedKPIs } from '../types'; import { getExperiment, updateExperiment, deleteExperiment, calculateExperimentPerformance, getExperimentAsins } 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); console.log('Calculation yielded performance delta payload:', perf); const updated = await updateExperiment(experiment.id, perf); console.log('Update return from backend:', updated); setExperiment(updated); } catch (e: any) { console.error('Full calculation error:', e); alert(`Error calculating results: ${e.message}`); } finally { setLoading(false); } }; const targetAsins = useMemo(() => { if (!experiment) return []; return Array.from(getExperimentAsins(experiment.asins, salesData)); }, [experiment, salesData]); const productBreakdown = useMemo(() => { if (!experiment || targetAsins.length <= 1 || !salesData.length) return null; const parseDate = (d?: string) => { if (!d) return new Date(); const [y, m, da] = d.split('T')[0].split('-'); return new Date(Number(y), Number(m) - 1, Number(da)); }; const startDate = parseDate(experiment.start_date); const endDate = experiment.end_date ? parseDate(experiment.end_date) : new Date(); const asinMap = new Map(); targetAsins.forEach(a => asinMap.set(a, { units: 0, revenue: 0, gv: 0, spend: 0 })); salesData.forEach(r => { const asin = (r.asin || '').toUpperCase(); if (!asinMap.has(asin)) return; const mkt = (r.marketplace || (r as any).customer || '').toLowerCase(); const isMarket = !experiment.marketplace || experiment.marketplace === 'All' || mkt.includes(experiment.marketplace.toLowerCase()); if (!isMarket) return; const recordDate = new Date(r.year, 0, 1 + ((r.week || 1) - 1) * 7); if (recordDate >= startDate && recordDate <= endDate) { const d = asinMap.get(asin)!; d.units += (r.unitsTotal ?? (r as any).units ?? 0); d.revenue += (r.salesTotal ?? (r as any).sellOut ?? 0); d.gv += r.glanceViews || 0; d.spend += r.cost || 0; } }); return Array.from(asinMap.entries()) .map(([asin, metrics]) => ({ asin, ...metrics, cvr: metrics.gv > 0 ? (metrics.units / metrics.gv) * 100 : 0, acos: metrics.revenue > 0 ? (metrics.spend / metrics.revenue) * 100 : 0 })) .filter(m => m.units > 0 || m.gv > 0 || m.spend > 0) .sort((a, b) => b[experiment.primary_metric === 'bsr' ? 'units' : experiment.primary_metric as 'units' | 'revenue' | 'gv' | 'cvr' | 'acos' || 'units'] - a[experiment.primary_metric === 'bsr' ? 'units' : experiment.primary_metric as 'units' | 'revenue' | 'gv' | 'cvr' | 'acos' || 'units']); }, [experiment, salesData, targetAsins]); const targetAsinsOriginal = experiment?.asins || []; const chartData = useMemo(() => { if (!experiment || !salesData.length) return []; const asinSet = new Set(targetAsins); const weeklyMap = new Map(); salesData.forEach(r => { const asin = (r.asin || '').toUpperCase(); if (!asinSet.has(asin)) return; const mkt = (r.marketplace || (r as any).customer || '').toLowerCase(); const isMarket = !experiment.marketplace || experiment.marketplace === 'All' || mkt.includes(experiment.marketplace.toLowerCase()); if (!isMarket) 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 ?? (r as any).units ?? 0); w.revenue += (r.salesTotal ?? (r as any).sellOut ?? 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 != null) && (
{experiment.type === 'pricing' && ( <> )} {experiment.type === 'advertising' && ( <> )} {experiment.type === 'content' && ( <> )} {experiment.type === 'promotion' && ( <> )} = 0 ? '+' : ''}${experiment.actual_lift_percent}%` : '—'} color={experiment.actual_lift_percent != null && experiment.actual_lift_percent >= 0 ? 'emerald' : 'red'} />
)} {/* Calculate Results Button */} {salesData.length > 0 && ( )} {/* Dynamic Metric Chart */} {chartData.length > 0 && (

Timeline Impact ({(experiment.primary_metric || 'units').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 ? ( <>