import React, { useState, useEffect, useMemo, useCallback } from 'react'; import { ExperimentListItem, ExperimentType, ExperimentStatus, CombinedKPIs } from '../types'; import { listExperiments, deleteExperiment, getExperimentStatusColor, getExperimentTypeColor, getExperimentIcon } from '../services/experiments'; import MultiSelectDropdown from './MultiSelectDropdown'; import { ExperimentBadge, ExperimentStatusBadge, ExperimentTypeBadge } from './ExperimentBadge'; import { LineChart, Line, ResponsiveContainer, YAxis } from 'recharts'; 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 ExperimentSparkline: React.FC<{ experiment: ExperimentListItem; salesData: CombinedKPIs[] }> = ({ experiment, salesData }) => { const chartData = useMemo(() => { if (!experiment.asins || !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(-12); }, [experiment, salesData]); if (chartData.length < 2) return
; const metric = experiment.primary_metric || 'units'; return (
); }; interface ExperimentsViewProps { onOpenDetail: (experimentId: string) => void; onOpenCreate: () => void; salesData?: CombinedKPIs[]; } const ExperimentsView: React.FC = ({ onOpenDetail, onOpenCreate, salesData = [] }) => { const [experiments, setExperiments] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // Filters const [statusFilter, setStatusFilter] = useState([]); const [typeFilter, setTypeFilter] = useState([]); const [marketplaceFilter, setMarketplaceFilter] = useState([]); const [searchQuery, setSearchQuery] = useState(''); const statusOptions = ['planned', 'active', 'completed', 'paused']; const typeOptions = ['pricing', 'advertising', 'content', 'promotion']; const marketplaceOptions = ['DE', 'UK', 'FR', 'IT', 'ES']; const loadExperiments = useCallback(async () => { setLoading(true); setError(null); try { const data = await listExperiments({ status: statusFilter.length > 0 ? statusFilter as ExperimentStatus[] : undefined, type: typeFilter.length > 0 ? typeFilter as ExperimentType[] : undefined, marketplace: marketplaceFilter.length > 0 ? marketplaceFilter : undefined, }); setExperiments(data); } catch (e: any) { setError(e.message); } finally { setLoading(false); } }, [statusFilter, typeFilter, marketplaceFilter]); useEffect(() => { loadExperiments(); }, [loadExperiments]); const filteredExperiments = useMemo(() => { if (!searchQuery) return experiments; const query = searchQuery.toLowerCase(); return experiments.filter(exp => exp.name.toLowerCase().includes(query) || exp.owner?.toLowerCase().includes(query) ); }, [experiments, searchQuery]); const handleDelete = async (id: string, e: React.MouseEvent) => { e.stopPropagation(); if (!confirm('Are you sure you want to delete this experiment?')) return; try { await deleteExperiment(id); loadExperiments(); } catch (e: any) { alert(`Error deleting experiment: ${e.message}`); } }; const handleDuplicate = async (experiment: ExperimentListItem, e: React.MouseEvent) => { e.stopPropagation(); onOpenDetail(experiment.id); // Duplicate functionality will be in detail view }; // Stats const stats = useMemo(() => ({ total: experiments.length, active: experiments.filter(e => e.status === 'active').length, planned: experiments.filter(e => e.status === 'planned').length, avgLift: experiments.filter(e => e.actual_lift_percent !== undefined) .reduce((sum, e) => sum + (e.actual_lift_percent || 0), 0) / Math.max(1, experiments.filter(e => e.actual_lift_percent !== undefined).length), }), [experiments]); return (
{/* Header */}

๐Ÿงช Experiments

Track and analyze your product experiments

{/* Stats Cards */}
{/* Filters */}
setSearchQuery(e.target.value)} className="bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-sm text-slate-200 placeholder-slate-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 flex-1 min-w-[200px]" /> {(statusFilter.length > 0 || typeFilter.length > 0 || marketplaceFilter.length > 0 || searchQuery) && ( )}
{/* Error State */} {error && (
Error: {error}
)} {/* Table */}
{loading ? (
) : filteredExperiments.length === 0 ? (
๐Ÿงช

No experiments yet

Create your first experiment to start tracking performance

) : (
{filteredExperiments.map((exp) => ( onOpenDetail(exp.id)} className="hover:bg-slate-800/30 cursor-pointer transition-colors" > ))}
Status Type Name Marketplace ASINs Duration Progress Impact Owner Actions
{exp.name} {exp.marketplace} {exp.asin_count} ASINs
{new Date(exp.start_date).toLocaleDateString()}
{exp.end_date && (
โ†’ {new Date(exp.end_date).toLocaleDateString()}
)}
{exp.progress_percent}%
{salesData.length > 0 && }
{exp.actual_lift_percent !== undefined ? ( = 0 ? 'text-emerald-400' : 'text-red-400'}`}> {exp.actual_lift_percent >= 0 ? '+' : ''}{exp.actual_lift_percent}% ) : ( โ€” )}
{exp.primary_metric || 'units'}
{exp.owner || 'โ€”'}
)}
); }; // Stat Card Component const StatCard: React.FC<{ label: string; value: string | number; color: string }> = ({ label, value, color }) => { const colorClasses: Record = { indigo: 'bg-indigo-500/10 border-indigo-500/30 text-indigo-400', emerald: 'bg-emerald-500/10 border-emerald-500/30 text-emerald-400', amber: 'bg-amber-500/10 border-amber-500/30 text-amber-400', purple: 'bg-purple-500/10 border-purple-500/30 text-purple-400', }; return (
{value}
{label}
); }; export default ExperimentsView;