mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 11:05:23 +02:00
New Features: - Experiments tab with full CRUD operations - Create experiments by ASIN or product line groups - Track pricing, advertising, content, and promotion experiments - Performance analytics with baseline vs experiment comparison - Visual experiment badges in Weekly Sales grid - Experiment detail view with metrics and learnings Technical Changes: - Add Supabase experiments table migration - New services/experiments.ts for CRUD + calculations - New components: ExperimentsView, ExperimentDetail, ExperimentForm, ExperimentBadge - Integrate experiment indicators in WeeklyGrid - Add navigation tab (desktop + mobile) - Type definitions in types.ts Usage: 1. Run SQL migration in Supabase 2. Navigate to Experiments tab 3. Create experiments with ASINs, dates, hypothesis 4. View performance lift after completion 5. See active experiments marked in Weekly Sales Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
116 lines
3.6 KiB
TypeScript
116 lines
3.6 KiB
TypeScript
import React from 'react';
|
|
import { ActiveExperiment, ExperimentStatus } from '../types';
|
|
import { getExperimentStatusColor, getExperimentTypeColor, getExperimentIcon } from '../services/experiments';
|
|
|
|
interface ExperimentBadgeProps {
|
|
experiments: ActiveExperiment[];
|
|
onClick?: (experimentId: string) => void;
|
|
}
|
|
|
|
export const ExperimentBadge: React.FC<ExperimentBadgeProps> = ({ experiments, onClick }) => {
|
|
if (!experiments || experiments.length === 0) return null;
|
|
|
|
const activeExp = experiments.find(e => e.status === 'active');
|
|
const plannedExp = experiments.find(e => e.status === 'planned');
|
|
const hasPast = experiments.some(e => e.status === 'completed' || e.status === 'paused');
|
|
|
|
// Priority: active > planned > past
|
|
const displayExp = activeExp || plannedExp || experiments[0];
|
|
|
|
const getStatusBadge = (status: ExperimentStatus) => {
|
|
switch (status) {
|
|
case 'active': return '🟢';
|
|
case 'planned': return '🟡';
|
|
case 'completed': return '⚪';
|
|
case 'paused': return '⏸️';
|
|
}
|
|
};
|
|
|
|
const daysText = displayExp.days_remaining !== undefined
|
|
? displayExp.days_remaining > 0
|
|
? `${displayExp.days_remaining}d left`
|
|
: 'Ending soon'
|
|
: '';
|
|
|
|
return (
|
|
<div className="inline-flex items-center gap-1">
|
|
{experiments.length > 1 && (
|
|
<span className="text-[9px] text-slate-500 mr-1">+{experiments.length}</span>
|
|
)}
|
|
<button
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
onClick?.(displayExp.experiment_id);
|
|
}}
|
|
className={`
|
|
inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[9px] font-medium
|
|
border ${getExperimentStatusColor(displayExp.status)}
|
|
hover:opacity-80 transition-opacity cursor-pointer
|
|
`}
|
|
title={`${displayExp.experiment_name}
|
|
Type: ${displayExp.type}
|
|
Start: ${displayExp.start_date}
|
|
End: ${displayExp.end_date || 'Ongoing'}
|
|
${daysText}`}
|
|
>
|
|
<span>{getExperimentIcon(displayExp.type)}</span>
|
|
<span className="hidden xl:inline truncate max-w-[80px]">{displayExp.experiment_name}</span>
|
|
{daysText && <span className="opacity-60">{daysText}</span>}
|
|
</button>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
interface ExperimentStatusBadgeProps {
|
|
status: ExperimentStatus;
|
|
size?: 'sm' | 'md' | 'lg';
|
|
}
|
|
|
|
export const ExperimentStatusBadge: React.FC<ExperimentStatusBadgeProps> = ({ status, size = 'md' }) => {
|
|
const sizeClasses = {
|
|
sm: 'px-1.5 py-0.5 text-[9px]',
|
|
md: 'px-2 py-1 text-xs',
|
|
lg: 'px-3 py-1.5 text-sm',
|
|
};
|
|
|
|
const statusLabels = {
|
|
active: 'Active',
|
|
planned: 'Planned',
|
|
completed: 'Completed',
|
|
paused: 'Paused',
|
|
};
|
|
|
|
return (
|
|
<span className={`inline-flex items-center gap-1 rounded-full font-medium border ${getExperimentStatusColor(status)} ${sizeClasses[size]}`}>
|
|
{getStatusBadge(status)} {statusLabels[status]}
|
|
</span>
|
|
);
|
|
};
|
|
|
|
interface ExperimentTypeBadgeProps {
|
|
type: string;
|
|
size?: 'sm' | 'md' | 'lg';
|
|
}
|
|
|
|
export const ExperimentTypeBadge: React.FC<ExperimentTypeBadgeProps> = ({ type, size = 'md' }) => {
|
|
const sizeClasses = {
|
|
sm: 'px-1.5 py-0.5 text-[9px]',
|
|
md: 'px-2 py-1 text-xs',
|
|
lg: 'px-3 py-1.5 text-sm',
|
|
};
|
|
|
|
const typeLabels = {
|
|
pricing: 'Pricing',
|
|
advertising: 'Advertising',
|
|
content: 'Content',
|
|
promotion: 'Promotion',
|
|
};
|
|
|
|
return (
|
|
<span className={`inline-flex items-center gap-1 rounded-full font-medium border ${getExperimentTypeColor(type as any)} ${sizeClasses[size]}`}>
|
|
<span>{getExperimentIcon(type as any)}</span>
|
|
{typeLabels[type as keyof typeof typeLabels] || type}
|
|
</span>
|
|
);
|
|
};
|